高品質の画像がたくさんあるアプリを作成して、画像を必要なサイズに縮小することにしました(つまり、画像が画面よりも大きい場合は、縮小します)。
一部のデバイスでは、画像を縮小するとぼやけたりピクセル化されたりしますが、同じデバイスでは、同じターゲットimageViewサイズで、画像が縮小されていなければ、問題なく表示されます。
この問題をさらに確認することにし、問題を示す小さなPOCアプリを作成しました。
コードを紹介する前に、これが私が話していることのデモです:
違いを確認するのは少し難しいですが、2番目は少しピクセル化されていることがわかります。これはどの画像にも表示できます。
public class MainActivity extends Activity
{
@Override
protected void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ImageView originalImageView=(ImageView)findViewById(R.id.originalImageView);
final ImageView halvedImageView=(ImageView)findViewById(R.id.halvedImageView);
final ImageView halvedBitmapImageView=(ImageView)findViewById(R.id.halvedBitmapImageView);
//
final Bitmap originalBitmap=BitmapFactory.decodeResource(getResources(),R.drawable.test);
originalImageView.setImageBitmap(originalBitmap);
halvedImageView.setImageBitmap(originalBitmap);
//
final LayoutParams layoutParams=halvedImageView.getLayoutParams();
layoutParams.width=originalBitmap.getWidth()/2;
layoutParams.height=originalBitmap.getHeight()/2;
halvedImageView.setLayoutParams(layoutParams);
//
final Options options=new Options();
options.inSampleSize=2;
// options.inDither=true; //didn't help
// options.inPreferQualityOverSpeed=true; //didn't help
final Bitmap bitmap=BitmapFactory.decodeResource(getResources(),R.drawable.test,options);
halvedBitmapImageView.setImageBitmap(bitmap);
}
}
xml:
<ScrollView xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:tools="http://schemas.Android.com/tools" Android:layout_width="match_parent"
Android:layout_height="match_parent" tools:context=".MainActivity"
Android:fillViewport="true">
<HorizontalScrollView Android:layout_width="match_parent"
Android:fillViewport="true" Android:layout_height="match_parent">
<LinearLayout Android:layout_width="match_parent"
Android:layout_height="match_parent" Android:orientation="vertical">
<TextView Android:layout_width="wrap_content"
Android:layout_height="wrap_content" Android:text="original" />
<ImageView Android:layout_width="wrap_content"
Android:id="@+id/originalImageView" Android:layout_height="wrap_content" />
<TextView Android:layout_width="wrap_content"
Android:layout_height="wrap_content" Android:text="original , imageView size is halved" />
<ImageView Android:layout_width="wrap_content"
Android:id="@+id/halvedImageView" Android:layout_height="wrap_content" />
<TextView Android:layout_width="wrap_content"
Android:layout_height="wrap_content" Android:text="bitmap size is halved" />
<ImageView Android:layout_width="wrap_content"
Android:id="@+id/halvedBitmapImageView" Android:layout_height="wrap_content" />
</LinearLayout>
</HorizontalScrollView>
</ScrollView>
なぜそれが起こるのですか?
両方の方法が同じソースから同じ係数を使用してサンプリングするため、両方の方法で同じ結果が得られるはずです。
ダウンサンプリング方式で遊んでみましたが、何の役にも立ちませんでした。
(inSampleSizeの代わりに)inDensityを使用すると修正されるようですが、何を設定すればよいかわかりません。外部の画像(たとえばインターネットから)の場合、使用したいサンプルサイズを掛けた画面密度に設定できると思います。
しかし、それは良い解決策でさえありますか?画像がresourcesフォルダー内にある場合はどうすればよいですか(ビットマップがどの密度フォルダーにあるかを取得する関数はないと思います)?推奨される方法( ここ について説明)を使用してもうまく機能しないのはなぜですか?
編集:リソースから取得するドローアブルに使用される密度を取得するためのトリックを見つけました( ここにリンク )。ただし、検出する密度を特定する必要があるため、将来の証拠にはなりません。
わかりました、私は素晴らしい代替案を見つけました。これはあらゆる種類のビットマップデコードで機能するはずです。
それだけでなく、2の検出力だけでなく、任意のサンプルサイズを使用してダウンスケールすることもできます。さらに力を入れれば、ダウンスケーリングに整数の代わりに分数を使用することもできます。
以下のコードは、resフォルダーの画像に対して機能しますが、あらゆる種類のビットマップデコードに対して簡単に実行できます。
private Bitmap downscaleBitmapUsingDensities(final int sampleSize,final int imageResId)
{
final Options bitmapOptions=new Options();
bitmapOptions.inDensity=sampleSize;
bitmapOptions.inTargetDensity=1;
final Bitmap scaledBitmap=BitmapFactory.decodeResource(getResources(),imageResId,bitmapOptions);
scaledBitmap.setDensity(Bitmap.DENSITY_NONE);
return scaledBitmap;
}
私はそれをテストしました、そしてそれはダウンサンプリングされた画像をうまく示しています。下の画像では、元の画像を示しており、inSampleSizeメソッドと私のメソッドを使用して画像を縮小しています。
違いはわかりにくいですが、密度を使用するものは、実際にはピクセルをスキップするだけでなく、すべてのピクセルを使用して考慮に入れます。少し遅いかもしれませんが、より正確で、より良い補間を使用します。
inSampleSizeを使用する場合と比較した場合の唯一の欠点は速度であるようです。これは、inSampleSizeがピクセルをスキップし、密度メソッドがスキップされたピクセルに対して追加の計算を行うため、inSampleSizeの方が優れています。
しかし、どういうわけかAndroidは両方のメソッドをほぼ同じ速度で実行すると思います。
2つの方法の比較は、 最も近い-隣接するダウンサンプリング と 双一次内挿ダウンサンプリング の比較に似ていると思います。
編集:グーグルが持っているものと比較して、私はここに示した方法の1つの欠点を見つけました。プロセス中に使用されるメモリはかなり高くなる可能性があり、それは画像自体に依存すると思います。つまり、意味があると思われる場合にのみ使用する必要があります。
編集:私はメモリの問題を克服したい人のためにマージされたソリューション(グーグルのソリューションと私の両方)を作りました。完璧ではありませんが、ダウンサンプリング中に元のビットマップが必要とするほど多くのメモリを使用しないため、以前よりも優れています。代わりに、Googleのソリューションで使用されているメモリを使用します。
コードは次のとおりです。
// as much as possible, use google's way to downsample:
bitmapOptions.inSampleSize = 1;
bitmapOptions.inDensity = 1;
bitmapOptions.inTargetDensity = 1;
while (bitmapOptions.inSampleSize * 2 <= inSampleSize)
bitmapOptions.inSampleSize *= 2;
// if google's way to downsample isn't enough, do some more :
if (bitmapOptions.inSampleSize != inSampleSize)
{
// downsample by bitmapOptions.inSampleSize/originalSampleSize .
bitmapOptions.inTargetDensity = bitmapOptions.inSampleSize;
bitmapOptions.inDensity = inSampleSize;
}
else if(sampleSize==1)
{
bitmapOptions.inTargetDensity=preferHeight ? reqHeight : reqWidth;
bitmapOptions.inDensity=preferHeight ? height : width;
}
つまり、両方の方法の長所と短所は次のとおりです。
Googleの方法(inSampleSizeを使用)は、デコード中に使用するメモリが少なく、高速です。ただし、グラフィックアーティファクトが発生することがあり、2の累乗へのダウンサンプリングしかサポートされていないため、結果のビットマップは必要以上に大きくなる可能性があります(たとえば、x1/7ではなくx1/4のサイズ)。
私の方法(密度を使用)は、より正確で、より高品質の画像を提供し、結果ビットマップで使用するメモリが少なくなります。ただし、デコード中に大量のメモリを使用する可能性があり(入力によって異なります)、少し遅くなります。
編集:別の改善。出力画像が必要なサイズ制限に一致しない場合があり、Googleの方法を使用してダウンサンプリングしすぎないようにする必要があることがわかりました。
final int newWidth = width / bitmapOptions.inSampleSize, newHeight = height / bitmapOptions.inSampleSize;
if (newWidth > reqWidth || newHeight > reqHeight) {
if (newWidth * reqHeight > newHeight * reqWidth) {
// prefer width, as the width ratio is larger
bitmapOptions.inTargetDensity = reqWidth;
bitmapOptions.inDensity = newWidth;
} else {
// prefer height
bitmapOptions.inTargetDensity = reqHeight;
bitmapOptions.inDensity = newHeight;
}
}
したがって、たとえば、2448x3264画像から1200x1200にダウンサンプリングすると、900x1200になります。
InSampleSizeを使用する必要があります。使用するサンプルサイズを判断するには、次の手順を実行します。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap map = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
int originalHeight = options.outHeight;
int originalWidth = options.outWidth;
// Calculate your sampleSize based on the requiredWidth and originalWidth
// For e.g you want the width to stay consistent at 500dp
int requiredWidth = 500 * getResources().getDisplayMetrics().density;
int sampleSize = originalWidth / requiredWidth;
// If the original image is smaller than required, don't sample
if(sampleSize < 1) { sampleSize = 1; }
options.inSampleSize = sampleSize;
options.inPurgeable = true;
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
お役に立てれば。
私にとっては、inSampleSizeを使用したダウンスケーリングのみがうまく機能しました(ただし、最近傍アルゴリズムとは異なります)。しかし残念ながら、これでは必要な正確な解像度を得ることができません(元の解像度のちょうど整数分の1)。
だから私は、この問題のソニーモバイルの解決策がそのようなタスクに最適であることがわかりました。
簡単に言うと、2つのステップで構成されています。
SonyMobileがこのタスクをどのように解決したかについての詳細な説明は次のとおりです。 http://developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-Android-application/
SonyMobile scale utilsのソースコードは次のとおりです。 http://developer.sonymobile.com/downloads/code-example-module/image-scaling-code-example-for-Android/