如何正确地对图像进行下采样? [英] How to downsample images correctly?

查看:23
本文介绍了如何正确地对图像进行下采样?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景

创建一个包含大量高质量图像的应用程序,我决定将图像缩小到所需的尺寸(这意味着如果图像大于屏幕,我将缩小它).

问题

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

我的尝试

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

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

有点难看出区别,但你可以看到第二个有点像素化.这可以显示在任何图像上.

公共类 MainActivity 扩展 Activity{@覆盖protected void onCreate(final Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);final ImageView originalImageView=(ImageView)findViewById(R.id.originalImageView);最终 ImageView halvedImageView=(ImageView)findViewById(R.id.halvedImageView);最终 ImageView halvedBitmapImageView=(ImageView)findViewById(R.id.halvedBitmapImageView);//最终位图 originalBitmap=BitmapFactory.decodeResource(getResources(),R.drawable.test);originalImageView.setImageBitmap(originalBitmap);halvedImageView.setImageBitmap(originalBitmap);//最终 LayoutParams layoutParams=halvedImageView.getLayoutParams();layoutParams.width=originalBitmap.getWidth()/2;layoutParams.height=originalBitmap.getHeight()/2;halvedImageView.setLayoutParams(layoutParams);//最终选项选项=新选项();options.inSampleSize=2;//options.inDither=true;//没有帮助//options.inPreferQualityOverSpeed=true;//没有帮助最终位图位图=BitmapFactory.decodeResource(getResources(),R.drawable.test,options);halvedBitmapImageView.setImageBitmap(位图);}}

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" 工具:context=".MainActivity"android:fillViewport="true"><Horizo​​ntalScrollView 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 大小减半"/><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="位图大小减半"/><ImageView android:layout_width="wrap_content"android:id="@+id/halvedBitmapImageView" android:layout_height="wrap_content"/></LinearLayout></Horizo​​ntalScrollView></ScrollView>

问题

为什么会发生?

这两种方法应该有相同的结果,因为两个样本来自相同的来源并使用相同的因子.

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

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

但这甚至是一个好的解决方案吗?如果图像位于资源文件夹内,我该怎么办(我认为没有功能可以获取位图所在的密度文件夹)?为什么它在使用推荐的方式时有效(在 和 双线性插值下采样.

与 Google 的方法相比,我发现这里展示的方法有一个缺点.过程中使用的内存可能会很高,我认为这取决于图像本身.这意味着您应该只在您认为有意义的情况下使用它.

<小时>

我为那些希望克服内存问题的人制作了一个合并的解决方案(谷歌的解决方案和我的解决方案).它并不完美,但比我之前所做的要好,因为它在下采样期间不会使用原始位图所需的内存.相反,它将使用谷歌解决方案中使用的内存.

代码如下:

//尽量使用google的方式下采样:bitmapOptions.inSampleSize = 1;bitmapOptions.inDensity = 1;bitmapOptions.inTargetDensity = 1;while (bitmapOptions.inSampleSize * 2 <= inSampleSize)bitmapOptions.inSampleSize *= 2;//如果谷歌的下采样方式还不够,请多做一些:如果 (bitmapOptions.inSampleSize != inSampleSize){//通过 bitmapOptions.inSampleSize/originalSampleSize 进行下采样.bitmapOptions.inTargetDensity = bitmapOptions.inSampleSize;bitmapOptions.inDensity = inSampleSize;}否则如果(样本大小== 1){bitmapOptions.inTargetDensity=preferHeight ?请求高度:请求宽度;bitmapOptions.inDensity=preferHeight ?高度宽度;}

简而言之,两种方法的优缺点:

Google 的方式(使用 inSampleSize)在解码期间使用更少的内存,并且速度更快.但是,它有时会导致一些图形失真,并且它只支持下采样到 2 的幂,因此结果位图可能需要比您想要的更多(例如 x1/4 而不是 x1/7 的大小).

我的方法(使用密度)更精确,提供更高质量的图像,并在结果位图上使用更少的内存.但是,它在解码过程中可能会使用大量内存(取决于输入),而且速度会慢一些.

<小时>

另一个改进,因为我发现在某些情况下输出图像与所需的大小限制不匹配,并且您不希望使用 Google 的方式进行过多的下采样:

 final int newWidth = width/bitmapOptions.inSampleSize, newHeight = height/bitmapOptions.inSampleSize;if (newWidth > reqWidth || newHeight > reqHeight) {if (newWidth * reqHeight > newHeight * reqWidth) {//更喜欢宽度,因为宽度比更大bitmapOptions.inTargetDensity = reqWidth;bitmapOptions.inDensity = newWidth;} 别的 {//喜欢高度bitmapOptions.inTargetDensity = reqHeight;bitmapOptions.inDensity = newHeight;}}

因此,例如,从 2448x3264 图像下采样到 1200x1200,它将变成 900x1200

Background

Creating an app that has a lot of high quality images, I've decided to downscale the images to the needed size (meaning that if the image is larger than the screen , I downscale it ) .

The problem

I've noticed that on some devices, if the images are downscaled, they become blurry/pixelated, yet on the same devices, for the same target imageView size, if the images aren't downscaled, they look just fine.

What I've tried

I've decided to check this issue further, and created a small POC app that shows the issue.

Before showing you code, here's a demo of what I'm talking about :

it's a bit hard to see the difference, but you can see that the second is a bit pixelated . this can be shown on any image.

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>

The question

Why does it occur?

Both methods should have the same result, as both sample from the same source and using the same factor.

I've tried to play with the downsampling method, but nothing has helped.

Using the inDensity (instead of inSampleSize) seems to fix it, but I'm not sure what to set for it . i think that for outside images (from the internet for example), i can set it to the screen density multiplied by the sample size i wish to use.

But is it even a good solution? what should i do in the case the images are inside the resources folder (i don't think there is a function to get which density folder a bitmap is located at) ? why does it work while using the recommended way (talked about here) doesn't work well ?


EDIT: I've found out a trick to get which density is used for a drawable you get from the resources (link here) . however, it isn't future proof, as you need to be specific to the density to detect.

解决方案

ok, i've found a nice alternative, which i think should work for any kind of bitmap decoding.

not only that, but it also allows you to downscale using any sample size you wish, and not just the power of 2 . if you put more effort to it, you can even use fractions instead of integers for the downscaling.

the code below works for images from the res folder, but it can easily be done for any kind of bitmap decoding:

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

i've tested it and it shows the downsampled images just fine. in the image below, i've shown the original image, and downscaling the image using teh inSampleSize method, and using my method.

it's hard to see the difference, but the one that uses the density actually doesn't just skip pixels, but uses all of them to take into account. it might be a bit slower, but it's more precise and uses a nicer interpolation.

the only disadvantage compared to using the inSampleSize seems to be speed, which is better on inSampleSize because inSampleSize skips pixels and because the densities method does extra calculations on the skipped pixels.

However, i think that somehow android runs both methods in about the same speed.

I think the 2 methods comparison is similar to the comparison between the nearest-neighbor downsampling and the bilinear-interpolation downsampling.

EDIT: i've found one downside of the method i've shown here, compared to the one Google has . the memory used during the process can be quite high, and i think it depends on the image itself. this means you should use it only on cases you think make sense.


EDIT: i've made a merged solution (both google's solution and mine) for those who wish to overcome the memory problem. it's not perfect, but it's better than what i did before, because it won't use as much memory as the original bitmap needs during the downsampling. instead, it will use the memory as used in google's solution.

here's the code:

    // 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;
      }

so, in short, the pros and cons of both methods:

Google's way (using inSampleSize) uses less memory during decoding, and is faster. However, it causes some graphical artifacts sometimes and it only supports downsampling to the power of 2, so the result bitmap might take more than what you wanted (for example size of x1/4 instead of x1/7) .

My way (using densities) is more precise, gives higher quality images, and uses less memory on the result bitmap. However, it can use a lot of memory during the decoding (depends on the input) and it's a bit slower.


EDIT: another improvement, as I've found that on some cases the output image doesn't match the required size restriction, and you don't wish to downsample too much using Google's way :

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

So, for example, downsampling from 2448x3264 image to 1200x1200, it will become 900x1200

这篇关于如何正确地对图像进行下采样?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆