記事一覧

Androidで画像のサイズを指定して縮小する方法 | Xamarin.Forms


今回は、画像ファイルのサイズが大きいとAndroid端末でOutOfMemoryのエラーが発生し、アプリがクラッシュしてしまう件について、前回の記事に引き続きご紹介していきます。iOSで画像のサイズを指定して縮小する方法については次回の記事にてご紹介いたします。
前回の記事ではListViewに表示されている画像をImageRendererで表示時に画像圧縮したものを表示し続けるという機能でした。しかしながら、一枚の画像サイズがあまりにも大きすぎるとバインドした時点でOutOfMemoryになってしまいました。そこで、そもそも画像を保存する際にサイズ縮小して、縮小された画像をListViewに表示すると良いのではと思いました。


xamarin_image_outofmemory_02.png


前提条件
・Windows10
・Visual Studio 2015 Community Update3
・Xamarin 4.3.0.784 (NuGet Xamarin.Forms 2.3.4.224)
・macOS Sierra 10.12.4 / Xcode8.3.1 / Xamarin.iOS 10.4.0.123


1.PCLの記述

Android特有の記述(Bitmapなど)を扱いますので、DependencyServiceにて記述します。
IImageService.cs
public interface IImageService
{
    Stream GetShrinkedStream(string filePath, int width, int height);
}


2.Androidでの記述


ImageService.cs
using System.IO;
using Android.Graphics;
[assembly: Dependency(typeof(ImageService))]
public class ImageService : IImageService
{
    public Stream GetShrinkedStream(string filePath, int width, int height)
    {
        Stream stream = null;
        try
        {
            //画像を圧縮する
            Bitmap bitmap = ImageService.GetShrinkedBitmap(filePath, width, height);

            //Streamを取得する
            stream = new MemoryStream();
            bitmap.Compress(Android.Graphics.Bitmap.CompressFormat.Png, 100, stream);  // this is the diff between iOS and Android
            stream.Position = 0;
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message + System.Environment.NewLine +
                                    ex.StackTrace);
        }
        return stream;
    }

    /// <summary>
    /// 縮小したBitmapを取得する
    /// </summary>
    /// <param name="source">ファイルパス</param>
    /// <param name="width">縮小サイズ(幅)</param>
    /// <param name="height">縮小サイズ(高さ)</param>
    /// <returns></returns>
    private static Bitmap GetShrinkedBitmap(string filePath, int width, int height)
    {
        Bitmap bitmap = null;
        try
        {
            //読み込み用のオプションオブジェクトを生成
            //この値をtrueにするとメモリに画像を読み込まず、画像のサイズ情報だけを取得することができます。
            var options = new BitmapFactory.Options { InJustDecodeBounds = true };

            //画像ファイル読み込み
            //オプションのInJustDecodeBoundsがtrueのため実際の画像はメモリに読み込まれません。
            ImageService.GetBitmap(filePath, options);

            //引数の縦幅に合わせた画像サイズを再計算します。
            //The with and height of the elements will be used to decode the image
            if (width > 0 && height > 0)
            {
                options.InSampleSize = ImageService.CalculateInSampleSize(options, width, height);
            }
            else
            {
                options.InSampleSize = ImageService.CalculateInSampleSize(options, options.OutWidth, options.OutHeight);
            }
            //読み込む画像の階調を指定して読み込む
            options.InPreferredConfig = Bitmap.Config.Argb8888;

            //画像をメモリに読み込む場合はfalseを指定
            options.InJustDecodeBounds = false;

            //これで指定した縮尺で画像を読み込めます。
            //容量も小さくなるのでOutOfMemoryが発生しにくいです。
            bitmap = ImageService.GetBitmap(filePath, options);

        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message + System.Environment.NewLine +
                                    ex.StackTrace);
        }
        return bitmap;
    }

    private static Bitmap GetBitmap(string filePath, BitmapFactory.Options options)
    {
        Bitmap bitmap = null;
        try
        {
            if (!String.IsNullOrEmpty(filePath))
            {
                bitmap = BitmapFactory.DecodeFile(filePath, options);
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message + System.Environment.NewLine +
                                    ex.StackTrace);
        }
        return bitmap;
    }

    private static int CalculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
    {
        // Raw height and width of image
        float height = options.OutHeight;
        float width = options.OutWidth;
        var inSampleSize = 1D;

        if (height > reqHeight || width > reqWidth)
{
int halfHeight = (int)(height / 2);
int halfWidth = (int)(width / 2);

// Calculate a inSampleSize that is a power of 2 - the decoder will use a value that is a power of two anyway.
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth)
{
inSampleSize *= 2;
}
}
return (int)inSampleSize;
    }
}

※CalculateInSampleSizeは前回の記事でもご紹介していますが、Xamarinの公式ページにて紹介されています。
https://developer.xamarin.com/recipes/android/resources/general/load_large_bitmaps_efficiently/


3.使用方法

DependencyServiceで取得したStreamをバイト配列に変換してそれをデータベース等に保存します。
※DBに保存する型は基本的にはバイト配列というのが通説です。
※さらにバイト配列から文字列に変換するにはSystem.Convert.ToBase64Stringで変換可能です。

TestPage.xaml.cs
if (this.imgPicture.Source.GetType() == typeof(FileImageSource))
{
    FileImageSource fis = (FileImageSource)this.imgPicture.Source;
    //Androidの場合、画像を圧縮する
    if (Device.RuntimePlatform == Device.Android)
    {
        Stream stream = DependencyService.Get<IImageService>().GetShrinkedStream(
                                fis.File,
                                (int)(this.imgPicture.WidthRequest),
                                (int)(this.imgPicture.HeightRequest)
                                );
        //ストリームをバイト配列に変換する。
//このバイト配列をDB等に保存します。
        byte[] smallSize = ImgConverter.GetByteArrayFromStream(stream);

//元サイズとの比較を出力
byte[] normalSize = DependencyService.Get<IFileService>().GetBytesFromFile(fis.File);
Debug.WriteLine("●normalSize Length=" + normalSize.Length.ToString() +
" StringLength=" + System.Convert.ToBase64String(normalSize).Length.ToString());
Debug.WriteLine("●smallSize Length=" + smallSize.Length.ToString() +
" StringLength=" + System.Convert.ToBase64String(smallSize).Length.ToString());
    }
}

※ストリームをバイト配列に変換する ImgConverter.GetByteArrayFromStreamについては以下の記事にてご紹介しています。
http://itblogdsi.blog.fc2.com/blog-entry-141.html

※DependencyService.Get<IFileService>().GetBytesFromFile()は次の.Net関数をDependencyServiceにて呼び出しています。
 System.IO.File.ReadAllBytes(string path)


出力結果

xamarin_image_outofmemory_03.png

通常のファイルだと Length=12828347 StringLength=17104464 の画像データを WidthRequest = 100, HeightRequest = 100 のサイズに縮小したところ、
縮小したデータは Length=98586 StringLength=131448 となっています。
かなり小さくなっていますね。




当ブログの内容をまとめた Xamarin逆引きメニュー は以下のURLからご覧になれます。
http://itblogdsi.blog.fc2.com/blog-entry-81.html


関連記事

コメント

コメントの投稿
非公開コメント

アルバム

広告

プロフィール

石河 純


著者名 :石河 純
自己紹介:素人上がりのIT技術者。趣味は卓球・車・ボウリング

IT関連の知識はざっくりとこんな感じです。
【OS関連】
WindowsServer: 2012/2008R2/2003/2000/NT4
Windows: 10/8/7/XP/2000/me/NT4/98
Linux: CentOS RedHatLinux9
Mac: macOS Sierra 10.12 / OSX Lion 10.7.5 / OSX Snow Leopard 10.6.8
【言語・データベース】
VB.net ASP.NET C#.net Java VBA
Xamarin.Forms
Oracle10g SQLServer2008R2 SQLAnywhere8/11/16
ActiveReport CrystalReport ReportNet(IBM)
【ネットワーク関連】
CCNP シスコ技術者認定
Cisco Catalyst シリーズ
Yamaha RTXシリーズ
FireWall関連
【WEB関連】
SEO SEM CSS IIS6/7 apache2

休みの日は卓球をやっています。
現在、卓球用品通販ショップは休業中です。