16

バックグラウンド

多くの高品質の画像を含むアプリを作成するため、画像を必要なサイズに縮小することにしました (つまり、画像が画面よりも大きい場合は縮小します)。

問題

一部のデバイスでは、画像が縮小されている場合、画像がぼやけたりピクセル化されたりしますが、同じデバイスで、同じターゲット 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を使用すると修正されるようですが、何を設定すればよいかわかりません。外部の画像(インターネットなど)の場合、使用したいサンプルサイズを掛けた画面密度に設定できると思います。

しかし、それは良い解決策ですか?画像がリソースフォルダー内にある場合はどうすればよいですか(ビットマップが配置されている密度フォルダーを取得する機能はないと思います)?推奨される方法(ここで説明)を使用してもうまくいかないのに、なぜうまくいくのですか?


編集:リソースから取得したドローアブルに使用される密度を取得するためのトリックを見つけました(リンクはこちら)。ただし、検出する密度に固有である必要があるため、将来の証明ではありません。

4

3 に答える 3

15

わかりました、私は素晴らしい代替手段を見つけました。これは、あらゆる種類のビットマップデコードで機能すると思います。

それだけでなく、 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 の方が優れています。

ただし、どういうわけかアンドロイドは両方のメソッドをほぼ同じ速度で実行すると思います。

2 つの方法の比較は、最近傍ダウンサンプリングバイリニア補間ダウンサンプリングの比較に似ていると思います。

編集: Google の方法と比較して、ここで示した方法の欠点が 1 つ見つかりました。プロセス中に使用されるメモリはかなり高くなる可能性があり、それは画像自体に依存すると思います。これは、意味があると思われる場合にのみ使用する必要があることを意味します。


編集:メモリの問題を克服したい人のために、マージされたソリューション(Googleのソリューションと私のソリューションの両方)を作成しました。完璧ではありませんが、ダウンサンプリング中に元のビットマップが必要とするほど多くのメモリを使用しないため、以前よりも優れています。代わりに、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 になります。

于 2013-07-21T14:24:47.807 に答える
0

私にとっては、inSampleSize を使用したダウンスケーリングのみがうまく機能しました (ただし、最近傍アルゴリズムとは異なります)。しかし残念なことに、これでは必要な正確な解像度を得ることができません (元の解像度よりも正確に整数倍小さいだけです)。

そのため、この問題に対する SonyMobile のソリューションがこのようなタスクに最適であることがわかりました。

簡単に言うと、次の 2 つのステップで構成されます。

  1. BitmapFactory.Options::inSampleSize->BitmapFactory.decodeResource()を使用して、必要な解像度にできるだけ近いが、それ未満ではない解像度にダウンスケールします
  2. Canvas::drawBitmap()を使用して少しダウンスケーリングすることで、正確な解像度を取得します。

SonyMobile がこのタスクをどのように解決したかについて詳しく説明します: http://developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-android-application/

SonyMobile スケール ユーティリティのソース コードは次のとおりです: http://developer.sonymobile.com/downloads/code-example-module/image-scaling-code-example-for-android/

于 2014-04-21T01:49:26.743 に答える