OutOfMemoryErrorなしで目的のサイズの画像を取得+ EXIFの向きに応じて自動回転

多くの人はおそらくすでにOutOfMemoryError問題に遭遇し、かなり賢明なDisplaying Bitmaps Efficientlyマニュアルを見つけました。 しかし、マニュアルに基づいて自転車を発明していない場合は、画像を受け取ることができる説明付きのターンキーソリューションを提供します。







使用例
ImageManager im = new ImageManager(ctx, 100, 100); Bitmap bm = im.setIsScale(true) .setIsResize(true) .setIsCrop(true) .getFromFile(myUri.toString());
      
      











OutOfMemoryError



なぜこのエラーが発生していますか? 問題は、各アプリケーションに対して、デバイスによって異なる限られた量のメモリ(ヒープサイズ)が割り当てられることです。 たとえば、16MB、24MB以上。 最近のデバイスには通常24MB以上が搭載されていますが、これらの値はすぐに「食べられる」可能性があります。



何がメモリを正確に吸収しますか? 答えはビットマップクラスにあります。これは通常、各ピクセルに2バイトまたは4バイトを使用します(画像のビットサイズに応じて-16ビットRGB_555または32ビットARGB_888)。 5メガピクセルのカメラで撮影した画像を含む、ビットマップの消費量を計算してみましょう。



アスペクト比4:3では、側面が2583 x 1936の画像が取得されますRGB_555構成では、ビットマップは2583 * 1936 * 2 = 9.54 MB(以下、MB = 2は20バイトのべき乗と考えます)、ARGB_888では2回かかります詳細-19MB強。 もっとメガピクセルのカメラを考えるのは怖いです。



解決策は短く明確です。


1)3番目のパラメーターを指定してBitmapFactory.decodeStream関数を使用し、inJustDecodeBounds = trueの新しいbitmapFactory.Options()を渡すと、ピクセル単位の画像寸法のみを含み、ピクセル自体を含まないビットマップを取得します。

2)必要な寸法を得るために画像を縮小する必要がある回数を決定します。

3)この値をBitmapFactory.OptionsインスタンスのinSampleSizeフィールドに割り当て、BitmapFactory.decodeStream関数を再度呼び出します。

4)デコーダーがOutOfMemoryErrorなしでサムネイルを返すことが保証されます



注:画像サイズを画面サイズより大きくする理由はありません。 また、多くのデバイスには16ビット画面があるため、ビットマップをARGB_888構成に保存する理由はありません。 しかし、よりカラフルな画面でも、メモリ消費量が2倍削減される利点は、画質(IMHO)がわずかに低下するよりも高くなります。



 InputStream in = ... // InputStream BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; BitmapFactory.decodeStream(in, null, o); in.close(); int origWidth = o.outWidth; //  int origHeight = o.outHeight; //  int bytesPerPixel = 2 // RGB_555  int maxSize = 480 * 800 * bytesPerPixel; //   Bitmap int desiredWidth = …; //  int desiredHeight = …; //  int desiredSize = _ desiredWidth * _ desiredHeight * bytesPerPixel; //   Bitmap   width  height if (desiredSize < maxSize) maxSize = desiredSize; int scale = 1; //  int origSize = origWidth * origHeight * bytesPerPixel; //   if (origWidth > origHeight) { scale = Math.round((float) origHeight / (float) desiredHeight); } else { scale = Math.round((float) origWidth / (float) desiredWidth); } o = new BitmapFactory.Options(); o.inSampleSize = scale; o.inPreferredConfig = Config.RGB_565; in = … // InputStream.  -     , .         InputStream   (  ByteArrayInputStream  FileInputStream). Bitmap bitmap = BitmapFactory.decodeStream(in, null, o); // Bitmap
      
      









次は?


幅と高さを正確に一致させる必要がない場合は、結果のビットマップで十分です。それ以外の場合は、画像のサイズを変更および/またはトリミングします。 これらの関数の実装は簡単で、ソースコードは投稿の最後にあります。



EXIFの向きまたは反転画像を修正します。



このソリューションは、jpeg形式にのみ適用されます。



画像内のオブジェクトが常に表示されるように回転するという保証はありません。 スマートフォンのカメラを任意の角度に回転させるだけで十分です。これは、他では使用しない画像です。 しかし、私は家と人々が地面に立って、空を飛んでいる鳥が欲しいです。 EXIFが助けになります-画像に追加情報を追加できる形式です。



私たちは、スマートフォンのスマートフォンがメタデータに追加する1つのパラメーターのみに興味があります(すべてのデバイスのカメラがこれを行うと信じたい)。 しかし、その生の形式では、回転の度合いではなく、1〜8のデジタル値を保存します。 これらの値の意味については、 ここで説明します 。 正直なところ、私はそれらの意味を暗記し始めなかったので、投稿の最後に既製の関数を取得することをお勧めします。これらの関数はgetOrientation(Context context、Uri uri)を度に変換します。 この関数は、90、180、270、0、または-1の値(何らかの理由で回転角度を判別できなかった場合のエラー状態)を返します。 したがって、値> 0の回転が必要です。



画像を正しい角度に戻すには、画像を取得するためのコードを補足する必要があります。



代わりに:



 int origWidth = o.outWidth; //  int origHeight = o.outHeight; // 
      
      







書く:



 int origWidth = 0; //  int origHeight = 0; //  if (orientation == 90 || orientation == 270) { origWidth = o.outHeight; origHeight = o.outWidth; } else { origWidth = o.outWidth; origHeight = o.outHeight; }
      
      







そして最後に追加します:



 if (orientation > 0) { Matrix matrix = new Matrix(); matrix.postRotate(orientation); Bitmap decodedBitmap = bitmap; bitmap = Bitmap.createBitmap(decodedBitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); //     if (decodedBitmap != null && !decodedBitmap.equals(bitmap)) { decodedBitmap.recycle(); } }
      
      







更新1



画像を最適化する



幅と高さ


ImageManagerクラスは、2つの列挙型ScaleModeとResizeModeを使用します。 それぞれにEQUAL_OR_GREATERとEQUAL_OR_LOWERの2つの値があります。



ScaleModeの場合:





ResizeModeの場合:





画像サイズ、メモリ消費、SDカード上のスペース、2g / 3gのダウンロード速度がセカンダリではない場合、EQUAL_OR_LOWERモードを使用することをお勧めします。これにより、画質がわずかに低下するため、これらのパラメータが減少します。 実際には、2gで画像を送受信するとき、速度は通常、多くを残しているため(特に、トンネルや地下鉄だけでなく不安定な信号の状態で)、この妥協案にすぐに気付きました。



Jpeg圧縮率


ただし、ScaleModeとResizeModeで幅と高さを「保存」できる場合は、イメージサイズを縮小する別の方法があります。jpeg圧縮率です。 これを行うために、ImageManagerクラスで、メソッドとそのオーバーロードを作成しました。



 byte[] getRawFromFile(String path, int compressRate) byte[] getRawFromFile(String path)
      
      







メソッドをハードコードでオーバーロードする際に、経験的に推測されたパラメーター75が使用されます。このメソッドをScaleModeおよびResizeModeと組み合わせて使用​​し、最適化されたjpegイメージをバイト配列の形式で取得します。



あとがき



このマニュアルが誰かに役立つだけでなく、理解を与えることを願っています。 軽率なコピーペーストは短期的には問題を解決できますが、長期的にはさらに大きな間違いにつながる可能性があります。



ImageManagerクラスリスト
 public final class ImageManager { private Context _ctx; private int _boxWidth; private int _boxHeight; private ResizeMode _resizeMode; private ScaleMode _scaleMode; private Config _rgbMode; private boolean _isScale; private boolean _isResize; private boolean _isCrop; private boolean _isRecycleSrcBitmap; private boolean _useOrientation; public ImageManager(Context ctx, int boxWidth, int boxHeight) { this(ctx); _boxWidth = boxWidth; _boxHeight = boxHeight; } public ImageManager(Context ctx) { _ctx = ctx; _isScale = false; _isResize = false; _isCrop = false; _isRecycleSrcBitmap = true; _useOrientation = false; } public ImageManager setResizeMode(ResizeMode mode) { _resizeMode = mode; return this; } public ImageManager setScaleMode(ScaleMode mode) { _scaleMode = mode; return this; } public ImageManager setRgbMode(Config mode) { _rgbMode = mode; return this; } public ImageManager setIsScale(boolean isScale) { _isScale = isScale; return this; } public ImageManager setIsResize(boolean isResize) { _isResize = isResize; return this; } public ImageManager setIsCrop(boolean isCrop) { _isCrop = isCrop; return this; } public ImageManager setUseOrientation(boolean value) { _useOrientation = value; return this; } public ImageManager setIsRecycleSrcBitmap(boolean value) { _isRecycleSrcBitmap = value; return this; } public Bitmap getFromFile(String path) { Uri uri = Uri.parse(path); int orientation = -1; if (_useOrientation) { orientation = getOrientation(_ctx, uri); } Bitmap bitmap = scale(new StreamFromFile(_ctx, path), orientation); return getFromBitmap(bitmap); } public Bitmap getFromBitmap(Bitmap bitmap) { if (bitmap == null) return null; if (_isResize) bitmap = resize(bitmap); if (_isCrop) bitmap = crop(bitmap); return bitmap; } public byte[] getRawFromFile(String path) { return getRawFromFile(path, 75); } public byte[] getRawFromFile(String path, int compressRate) { Bitmap scaledBitmap = getFromFile(path); if (scaledBitmap == null) return null; ByteArrayOutputStream output = new ByteArrayOutputStream(); scaledBitmap.compress(CompressFormat.JPEG, compressRate, output); recycleBitmap(scaledBitmap); byte[] rawImage = output.toByteArray(); if (rawImage == null) { return null; } return rawImage; } public Bitmap getFromByteArray(byte[] rawImage) { Bitmap bitmap = scale(new StreamFromByteArray(rawImage), -1); return getFromBitmap(bitmap); } @SuppressLint("NewApi") private Bitmap scale(IStreamGetter streamGetter, int orientation) { try { InputStream in = streamGetter.Get(); if (in == null) return null; Bitmap bitmap = null; Config rgbMode = _rgbMode != null ? _rgbMode : Config.RGB_565; if (!_isScale) { BitmapFactory.Options o = new BitmapFactory.Options(); o.inPreferredConfig = rgbMode; if (android.os.Build.VERSION.SDK_INT >= 11) { o.inMutable = true; } bitmap = BitmapFactory.decodeStream(in, null, o); in.close(); return bitmap; } if (_boxWidth == 0 || _boxHeight == 0) { if (in != null) in.close(); return null; } ScaleMode scaleMode = _scaleMode != null ? _scaleMode : ScaleMode.EQUAL_OR_GREATER; int bytesPerPixel = rgbMode == Config.ARGB_8888 ? 4 : 2; int maxSize = 480 * 800 * bytesPerPixel; int desiredSize = _boxWidth * _boxHeight * bytesPerPixel; if (desiredSize < maxSize) maxSize = desiredSize; BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; BitmapFactory.decodeStream(in, null, o); in.close(); int scale = 1; int origWidth; int origHeight; if (orientation == 90 || orientation == 270) { origWidth = o.outHeight; origHeight = o.outWidth; } else { origWidth = o.outWidth; origHeight = o.outHeight; } while ((origWidth * origHeight * bytesPerPixel) * (1 / Math.pow(scale, 2)) > maxSize) { scale++; } if (scaleMode == ScaleMode.EQUAL_OR_LOWER) { scale++; } o = new BitmapFactory.Options(); o.inSampleSize = scale; o.inPreferredConfig = rgbMode; in = streamGetter.Get(); if (in == null) return null; bitmap = BitmapFactory.decodeStream(in, null, o); in.close(); if (orientation > 0) { Matrix matrix = new Matrix(); matrix.postRotate(orientation); Bitmap decodedBitmap = bitmap; bitmap = Bitmap.createBitmap(decodedBitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); if (decodedBitmap != null && !decodedBitmap.equals(bitmap)) { recycleBitmap(decodedBitmap); } } return bitmap; } catch (IOException e) { return null; } } private Bitmap resize(Bitmap sourceBitmap) { if (sourceBitmap == null) return null; if (_resizeMode == null) _resizeMode = ResizeMode.EQUAL_OR_GREATER; float srcRatio; float boxRatio; int srcWidth = 0; int srcHeight = 0; int resizedWidth = 0; int resizedHeight = 0; srcWidth = sourceBitmap.getWidth(); srcHeight = sourceBitmap.getHeight(); if (_resizeMode == ResizeMode.EQUAL_OR_GREATER && (srcWidth <= _boxWidth || srcHeight <= _boxHeight) || _resizeMode == ResizeMode.EQUAL_OR_LOWER && srcWidth <= _boxWidth && srcHeight <= _boxHeight) { return sourceBitmap; } srcRatio = (float)srcWidth / (float)srcHeight; boxRatio = (float)_boxWidth / (float)_boxHeight; if (srcRatio > boxRatio && _resizeMode == ResizeMode.EQUAL_OR_GREATER || srcRatio < boxRatio && _resizeMode == ResizeMode.EQUAL_OR_LOWER) { resizedHeight = _boxHeight; resizedWidth = (int)((float)resizedHeight * srcRatio); } else { resizedWidth = _boxWidth; resizedHeight = (int)((float)resizedWidth / srcRatio); } Bitmap resizedBitmap = Bitmap.createScaledBitmap(sourceBitmap, resizedWidth, resizedHeight, true); if (_isRecycleSrcBitmap && !sourceBitmap.equals(resizedBitmap)) { recycleBitmap(sourceBitmap); } return resizedBitmap; } private Bitmap crop(Bitmap sourceBitmap) { if (sourceBitmap == null) return null; int srcWidth = sourceBitmap.getWidth(); int srcHeight = sourceBitmap.getHeight(); int croppedX = 0; int croppedY = 0; croppedX = (srcWidth > _boxWidth) ? (int)((srcWidth - _boxWidth) / 2) : 0; croppedY = (srcHeight > _boxHeight) ? (int)((srcHeight - _boxHeight) / 2) : 0; if (croppedX == 0 && croppedY == 0) return sourceBitmap; Bitmap croppedBitmap = null; try { croppedBitmap = Bitmap.createBitmap(sourceBitmap, croppedX, croppedY, _boxWidth, _boxHeight); } catch(Exception e) { } if (_isRecycleSrcBitmap && !sourceBitmap.equals(croppedBitmap)) { recycleBitmap(sourceBitmap); } return croppedBitmap; } public static void recycleBitmap(Bitmap bitmap) { if (bitmap == null || bitmap.isRecycled()) return; bitmap.recycle(); System.gc(); } private static interface IStreamGetter { public InputStream Get(); } private static class StreamFromFile implements IStreamGetter { private String _path; private Context _ctx; public StreamFromFile(Context ctx, String path) { _path = path; _ctx = ctx; } @SuppressWarnings("resource") public InputStream Get() { try { Uri uri = Uri.parse(_path); return "content".equals(uri.getScheme()) ? _ctx.getContentResolver().openInputStream(uri) : new FileInputStream(_path); } catch (FileNotFoundException e) { return null; } } } private static class StreamFromByteArray implements IStreamGetter { private byte[] _rawImage; public StreamFromByteArray(byte[] rawImage) { _rawImage = rawImage; } public InputStream Get() { if (_rawImage == null) return null; return new ByteArrayInputStream(_rawImage); } } private static int getOrientation(Context context, Uri uri) { if ("content".equals(uri.getScheme())) { Cursor cursor = context.getContentResolver().query(uri, new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null); if (cursor == null || cursor.getCount() != 1) { return -1; } cursor.moveToFirst(); int orientation = cursor.getInt(0); cursor.close(); return orientation; } else { try { ExifInterface exif = new ExifInterface(uri.getPath()); int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1); switch (orientation) { case ExifInterface.ORIENTATION_ROTATE_270: return 270; case ExifInterface.ORIENTATION_ROTATE_180: return 180; case ExifInterface.ORIENTATION_ROTATE_90: return 90; case ExifInterface.ORIENTATION_NORMAL: return 0; default: return -1; } } catch (IOException e) { return -1; } } } }
      
      








All Articles