背景
创建,有很多的高品质图像的应用程序,我已经决定了图像缩减到所需的大小(这意味着如果图像比屏幕大,我缩减它)。
问题
我注意到,在某些设备上,如果将图像按比例缩小,他们变得模糊/像素化,但在相同的设备上,对同一目标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可以将其设置到屏幕密度乘以采样大小我希望使用。
但是,它甚至好的解决办法? 我应该在图像的资源文件夹内的情况下做的(我不认为这是得到其密度文件夹中的位图位于一个功能)? 为什么在使用推荐的方式(谈论它工作在这里 )不能很好地工作?
编辑:我发现了一个窍门要获取的密度用于您从资源(得到绘制链接在这里 )。 然而,它不是面向未来的,因为你需要特定的密度来检测。
好吧,我已经找到了一个不错的选择,我认为应该对任何类型的位图解码的工作。
不仅如此,但它也可以让你使用任何你想要的样本量缩减,而不是仅仅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
你应该用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表现很好(但不喜欢近邻算法)缩减。 但不幸的是,这并没有让让,我们需要(只是正好整数倍比原来更小)精确的分辨率。
所以,我发现这个问题的SonyMobile的解决方案适用的最适合这样的任务。
简单地说,它包括2个步骤:
- 低档次的使用BitmapFactory.Options :: inSampleSize-> BitmapFactory.decodeResource()尽可能接近到你所需要的分辨率,但没有比它少
- 得到精确的分辨率,通过缩减使用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/