如何正确下采样图像?(How to downsample images correctly?)

2019-09-02 03:03发布

背景

创建,有很多的高品质图像的应用程序,我已经决定了图像缩减到所需的大小(这意味着如果图像比屏幕大,我缩减它)。

问题

我注意到,在某些设备上,如果将图像按比例缩小,他们变得模糊/像素化,但在相同的设备上,对同一目标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);
    }
  }

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)似乎修复它,但我不知道该怎么为它设置。 我认为,对于外部图像(来自(例如)因特网)中,i可以将其设置到屏幕密度乘以采样大小我希望使用。

但是,它甚至好的解决办法? 我应该在图像的资源文件夹内的情况下做的(我不认为这是得到其密度文件夹中的位图位于一个功能)? 为什么在使用推荐的方式(谈论它工作在这里 )不能很好地工作?


编辑:我发现了一个窍门要获取的密度用于您从资源(得到绘制链接在这里 )。 然而,它不是面向未来的,因为你需要特定的密度来检测。

Answer 1:

好吧,我已经找到了一个不错的选择,我认为应该对任何类型的位图解码的工作。

不仅如此,但它也可以让你使用任何你想要的样本量缩减,而不是仅仅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;
  }

我测试过它,它显示了采样图像就好了。 下面的图片中,我展示了原始图像,并使用TEH inSampleSize方法缩减图像,并使用我的方法。

很难看出差别,但使用的密度其实并不只是跳过像素,但使用所有这些的一个考虑。 它可能是一个有点慢,但它更精确,使用更好的插值。

与使用inSampleSize唯一的缺点似乎是速度,这是inSampleSize更好,因为inSampleSize跳过像素,并且因为密度方法确实在跳过像素额外的计算。

不过,我觉得,不知怎的,运行Android在大约相同的速度两种方法。

我认为2种方法相比是类似的比较近邻下采样和双线性插值下采样 。

编辑:我发现我在这里所示的方法的一个缺点,相对于一个谷歌了。 过程中使用的内存可以说是相当高的,而且我认为这取决于图像本身上。 这意味着你应该使用它只有在情况下,您认为是有意义的。


编辑:我做了一个合并后的溶液(谷歌的解决方案和矿山两者)为那些谁愿意克服内存问题。 它并不完美,但它是不是我做了什么以前好,因为原来的位图减采样时需要它不会使用尽可能多的内存。 相反,它会使用存储在谷歌的解决方案中使用。

下面的代码:

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

所以,总之,这两种方法的优缺点:

解码期间谷歌的方式(使用inSampleSize)使用较少的内存,而且速度更快。 但是,它会导致一些图形文物有时,它仅支持下采样,以2的幂,所以结果可能位时间比你想要的更多的(对于x1 / 4,而不是X1 / 7的例子大小)。

(使用密度)我的方法是更精确的,给人更高质量的图像,并且在结果位图使用较少的内存。 然而,它可以使用大量的存储器中的解码期间(取决于输入),它是一个有点慢。


编辑:另一种改进,因为我发现,在某些情况下,输出图像不符合要求的尺寸限制,你不希望下采样过多使用谷歌的方式:

    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



Answer 2:

你应该用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);

希望这可以帮助。



Answer 3:

对我来说,只有使用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规模utils的源代码: http://developer.sonymobile.com/downloads/code-example-module/image-scaling-code-example-for-android/



文章来源: How to downsample images correctly?