如何正确对图像进行下采样?

背景

创建具有大量高质量图像的应用程序后,我决定将图像缩小到所需的大小(这意味着如果图像大于屏幕,则将其缩小)。


问题

我注意到,在某些设备上,如果图像按比例缩小,则它们会变得模糊/像素化,但是在相同的设备上,对于相同的目标imageView大小,如果图像没有按比例缩小,它们看起来就很好。


我尝试过的

我决定进一步检查此问题,并创建了一个显示该问题的小型POC应用程序。


在向您展示代码之前,这是我正在谈论的演示:


在此处输入图片说明


很难看出区别,但是您可以看到第二个像素化了。这可以显示在任何图像上。


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);

    }

  }


问题

为什么会发生?


两种方法都应具有相同的结果,因为两种样品均来自同一来源且使用相同因子。


我尝试使用下采样方法,但没有任何帮助。


使用inDensity(而不是inSampleSize)似乎可以解决此问题,但是我不确定该为它设置什么。我认为对于外部图像(例如,来自互联网),我可以将其设置为屏幕密度乘以我希望使用的样本大小。


但这甚至是一个好的解决方案吗?如果图像位于资源文件夹中,该怎么办(我不认为有一个函数可以获取位图位于哪个密度文件夹中)?为什么在使用推荐的方法(在此讨论)时不能正常工作?


编辑:我发现了一个技巧,可以从资源中获得用于绘制的密度(从此处链接)。但是,这并不是未来的证明,因为您需要特定于要检测的密度。


倚天杖
浏览 2678回答 3
3回答

忽然笑

好的,我找到了一个不错的选择,我认为它应该适用于任何类型的位图解码。不仅如此,它还允许您使用所需的任何样本大小(而不只是2的幂)进行缩减。如果您付出更多努力,甚至可以使用分数而不是整数进行缩小。下面的代码适用于res文件夹中的图像,但是对于任何类型的位图解码都可以轻松完成:private Bitmap downscaleBitmapUsingDensities(final int sampleSize,final int imageResId)&nbsp; {&nbsp; final Options bitmapOptions=new Options();&nbsp; bitmapOptions.inDensity=sampleSize;&nbsp; bitmapOptions.inTargetDensity=1;&nbsp; final Bitmap scaledBitmap=BitmapFactory.decodeResource(getResources(),imageResId,bitmapOptions);&nbsp; scaledBitmap.setDensity(Bitmap.DENSITY_NONE);&nbsp; return scaledBitmap;&nbsp; }我已经测试过了,它显示了降采样的图像就好了。在下面的图像中,我显示了原始图像,并使用inSampleSize方法和我的方法缩小了图像的尺寸。很难看到差异,但是使用密度的像素实际上并不仅仅是跳过像素,而是将所有像素都考虑在内。它可能会慢一些,但它更精确,并且使用了更好的插值法。在此处输入图片说明与使用inSampleSize相比,唯一的缺点似乎是速度,它在inSampleSize上更好,因为inSampleSize会跳过像素,并且因为密度方法会对跳过的像素进行额外的计算。但是,我认为android以某种相同的速度运行这两种方法。我认为这两种方法的比较类似于最近邻降采样与双线性插值降采样之间的比较。编辑:与Google相比,我发现了这里显示的方法的一个缺点。在此过程中使用的内存可能很高,我认为这取决于图像本身。这意味着您仅应在您认为合理的情况下使用它。编辑:对于那些希望克服内存问题的人,我已经制定了合并的解决方案(包括Google的解决方案和我的解决方案)。它不是完美的,但是比我以前做的要好,因为它在下采样期间不会使用原始位图所需的内存。相反,它将使用Google解决方案中使用的内存。这是代码:&nbsp; &nbsp; // as much as possible, use google's way to downsample:&nbsp; &nbsp; bitmapOptions.inSampleSize = 1;&nbsp; &nbsp; bitmapOptions.inDensity = 1;&nbsp; &nbsp; bitmapOptions.inTargetDensity = 1;&nbsp; &nbsp; while (bitmapOptions.inSampleSize * 2 <= inSampleSize)&nbsp; &nbsp; &nbsp; &nbsp; bitmapOptions.inSampleSize *= 2;&nbsp; &nbsp; // if google's way to downsample isn't enough, do some more :&nbsp; &nbsp; if (bitmapOptions.inSampleSize != inSampleSize)&nbsp;&nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; // downsample by bitmapOptions.inSampleSize/originalSampleSize .&nbsp; &nbsp; &nbsp; bitmapOptions.inTargetDensity = bitmapOptions.inSampleSize;&nbsp; &nbsp; &nbsp; bitmapOptions.inDensity = inSampleSize;&nbsp; &nbsp; &nbsp; }&nbsp;&nbsp; &nbsp; else if(sampleSize==1)&nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; bitmapOptions.inTargetDensity=preferHeight ? reqHeight : reqWidth;&nbsp; &nbsp; &nbsp; bitmapOptions.inDensity=preferHeight ? height : width;&nbsp; &nbsp; &nbsp; }因此,简而言之,两种方法的优缺点:Google的方式(使用inSampleSize)在解码期间使用较少的内存,并且速度更快。但是,它有时会导致一些图形失真,并且仅支持下采样到2的幂次,因此结果位图可能会花费比您想要的更多的值(例如,x1 / 4的大小而不是x1 / 7的大小)。我的方法(使用密度)更加精确,可以提供更高质量的图像,并且在结果位图上使用的内存更少。但是,它在解码期间可能会占用大量内存(取决于输入),并且速度稍慢。编辑:另一项改进,因为我发现在某些情况下输出图像与所需的尺寸限制不符,并且您不希望使用Google的方式对太多像素进行下采样:&nbsp; &nbsp; final int newWidth = width / bitmapOptions.inSampleSize, newHeight = height / bitmapOptions.inSampleSize;&nbsp; &nbsp; if (newWidth > reqWidth || newHeight > reqHeight) {&nbsp; &nbsp; &nbsp; &nbsp; if (newWidth * reqHeight > newHeight * reqWidth) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // prefer width, as the width ratio is larger&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bitmapOptions.inTargetDensity = reqWidth;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bitmapOptions.inDensity = newWidth;&nbsp; &nbsp; &nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // prefer height&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bitmapOptions.inTargetDensity = reqHeight;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bitmapOptions.inDensity = newHeight;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }因此,例如,从2448x3264图像降采样到1200x1200,它将变为900x1200
打开App,查看更多内容
随时随地看视频慕课网APP