位图太大,无法上传到纹理。

159

我正在将一张位图加载到ImageView中,但遇到了这个错误。我了解到这个限制与OpenGL硬件纹理的大小限制有关(2048x2048)。我需要加载的图片是一个大约有4000个像素高度的缩放图片。

我尝试在清单文件中关闭硬件加速,但没有成功。

    <application
        android:hardwareAccelerated="false"
        ....
        >

是否可以将大小超过2048像素的图像加载到ImageView中?


1
对于任何在这里寻找答案的人,请不要忘记将您的图像放入滚动视图中,如果您想要它可以滚动的话。这样就可以消除错误了。我之前浪费了一些时间才意识到这是我的问题所在。 - Jason Ridge
对于任何想要显示大图像并仍保持图像质量的人,请参考下面@stoefln答案中的库。我使用过它,值得一试。绝对比“inSampleSize”方法更好。 - Mahendra Liya
1
@Joe Blow,当前的答案对您没有起作用吗?如果不是,那么请详细说明您在此问题的情况下遇到的问题。 - Pravin Divraniya
1
嘿 @VC.One - 关于我的那件事情让你感到不爽,确实有些奇怪。我应该点“Reward existing answer”按钮的。对于那个SO弹出窗口上的“最佳”按钮,因为它很愚蠢,所以没有人会费心地去单击。现在问题已经解决了,因为我点击了悬赏按钮。谢谢! - Fattie
1
我尽量总是支付赏金(我不喜欢积攒积分)。我猜,如果你点击我的个人资料,然后选择赏金,@VC.One,你会看到多年来来自SO的许多非常棒的问答!!!!!!! - Fattie
显示剩余5条评论
18个回答

413

这并不是直接回答问题(加载大于2048的图像)的方式,而是为那些遇到这个错误的人提供一个可能的解决方案。

在我的情况下,图像在两个维度上都小于2048(确切地说是1280x727),并且问题特别出现在Galaxy Nexus上。该图像位于 drawable 文件夹中,没有任何合格的文件夹。 Android假定没有密度限定符的drawable是mdpi,并将它们缩放到其他密度,例如在这种情况下将xhdpi缩放2倍。将有问题的图片移动到 drawable-nodpi 中以防止缩放可以解决问题。


3
我尝试加载可绘制对象时遇到了内存问题。花了两个小时来理解为什么会出现这种情况。感谢你的间接答案。 - TrueCoke
3
我现在已经在Android上面兼职编程将近三个半月了,直到现在才意识到一个问题。把'drawable'本质上变成了隐藏的'drawable-mdpi'文件夹实在是太愚蠢了。这也解释了为什么我的自定义地图标记看起来很糟糕(它们被放大然后缩小)。 - theblang
10
是啊,到底怎么回事!我认为99%的安卓程序员都认为“drawable”意味着“不要缩放这个”。 - Matt Logan
但是我正在从相机中点击图像并使用 setImageBitmap 来设置图像。在这种情况下该怎么办? - Tushar Gogna
我把有问题的图片放在了drawable文件夹中。我将它移动到了drawable-hdpi文件夹中,现在它可以正常工作了。谢谢! - user2230662
@JoeBlow 哇,谢谢!我当初发布答案时没想到它会成为如此重要的事情。很高兴我能帮助这么多人! - Pilot_51

108

我是这样缩小图像的:

ImageView iv  = (ImageView)waypointListView.findViewById(R.id.waypoint_picker_photo);
Bitmap d = new BitmapDrawable(ctx.getResources() , w.photo.getAbsolutePath()).getBitmap();
int nh = (int) ( d.getHeight() * (512.0 / d.getWidth()) );
Bitmap scaled = Bitmap.createScaledBitmap(d, 512, nh, true);
iv.setImageBitmap(scaled);

2
谢谢,createScaledBitmap对我很有帮助,使我能够显示一个过大的位图。 - Boy
问题已解决,谢谢。实际上,我正在使用 Samsung Galaxy S4 4.4.2 版本从相机中获取图像并在 ImageView 中显示。 - Mitesh Shah
抱歉,我无法检测出"w"是什么。 - Bobbelinio
@iGude "w.photo.getAbsolutePath()" 是图片路径。 - MorZa
1
抱歉,这个 ctx 是什么意思? - sabith.ak
1
@AmeerSabith 看起来 ctx 是一个上下文对象(例如你正在运行的 Activity)。 - Matt

51

所有渲染都基于OpenGL,因此您不能超过此限制(GL_MAX_TEXTURE_SIZE取决于设备,但最小值为2048x2048,因此任何尺寸小于2048x2048的图像都适合)。

对于如此大的图像,如果您想在移动设备上进行缩放,应该设置类似于Google地图的系统。将图像分成几个部分,并定义多个分辨率。

或者,在显示图像之前可以将其缩小(请参见此问题上user1352407的答案 )。

另外,要注意将图像放入哪个文件夹中,Android可能会自动缩放图像。请参阅此问题下面Pilot_51的答案


23
如果是这种情况,画廊应用程序如何允许显示和处理使用相机拍摄的图像?2048x2048只是4MP的图像,许多安卓手机拍摄的照片比这个大得多,但画廊应用似乎没有问题。 - Ollie C
3
因为GL_MAX_TEXTURE_SIZE取决于设备。 - jptsetung
24
这真的毫无意义。我现在遇到了相同的问题 - 使用一个1286x835像素的图像。而且:只有在Galaxy Nexus上才会显示这个错误信息,没有图片!一个顶尖的智能手机不能显示如此小的图像看起来很荒谬!我的HTC Hero可以显示那个!我该怎么办? - Zordid
1
请查看Romain在这里的答案:https://dev59.com/X2s05IYBdhLWcg3wIOfS - Ben Lee
4
@OllieC 我也想知道画廊应用是如何做到的。如果有人知道或者有展示大图的例子,那就太好了。 - Innova
显示剩余2条评论

34

为什么要花费大量时间手动编写/调试所有这些降采样代码,不如使用Picasso?它是专门用来处理各种类型和/或大小的位图的。

我只用了这一行代码就解决了我的"位图太大..."问题:

Picasso.load(resourceId).fit().centerCrop().into(imageView);

在不调用resize()的情况下使用centerCrop()将导致IllegalStateException异常。当使用中心裁剪时,库强制调用resize。 - Zsolt Boldizsar
这是非常有道理的,因为裁剪意味着你正在调整图像的大小。 - Phileo99
2
快速、简单的解决方案。不确定它是否是最好的,但我得到了我想要的。非常感谢。 - Nabin
当使用Picasso的目标时,对于更大尺寸的图像仍然会出现相同的错误 :( - Narendra Singh
尝试使用版本2.5.3-SNAPSHOT,似乎现在可以正确处理大图像。 - Anton Malmygin
显示剩余2条评论

23

AndroidManifest.xml 中添加以下 2 个属性对我有用:

android:largeHeap="true"
android:hardwareAccelerated="false"

这解决了我在三星设备上的问题。谢谢。 - Hitesh Bisht

16
将图像文件从drawable文件夹更改为drawable-nodpi文件夹对我有用。

这样做可以防止Android根据设备的尺寸和屏幕密度自动缩放图像;这就是为什么这个解决方案有效的原因。Android会尝试自动缩放drawable文件夹中的任何内容。 - Chris Cirefice

9

我使用了Picasso,但遇到了同样的问题。在宽度或高度中至少有一个图片太大了。最终我在这里找到了解决办法。您可以根据显示大小将大图缩小,并保持宽高比:

    public Point getDisplaySize(Display display) {
    Point size = new Point();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
        display.getSize(size);
    } else {
        int width = display.getWidth();
        int height = display.getHeight();
        size = new Point(width, height);
    }

    return size;
}

并使用Picasso加载图片的方法:

    final Point displySize = getDisplaySize(getWindowManager().getDefaultDisplay());
        final int size = (int) Math.ceil(Math.sqrt(displySize.x * displySize.y));
        Picasso.with(this)
                .load(urlSource)
                .resize(size, size)
                .centerInside()
                .into(imageViewd);

为了获得更好的性能,您可以根据显示屏的宽度和高度下载图片,而不是整个图片:
    public String reviseImageUrl(final Integer displayWidth,     final Integer displayHeight,
        final String originalImageUrl) {
    final String revisedImageUrl;

    if (displayWidth == null && displayHeight == null) {
        revisedImageUrl = originalImageUrl;
    } else {
        final Uri.Builder uriBuilder = Uri.parse(originalImageUrl).buildUpon();

        if (displayWidth != null && displayWidth > 0) {
            uriBuilder.appendQueryParameter(QUERY_KEY_DISPLAY_WIDTH, String.valueOf(displayWidth));
        }

        if (displayHeight != null && displayHeight > 0) {
            uriBuilder.appendQueryParameter(QUERY_KEY_DISPLAY_HEIGHT, String.valueOf(displayHeight));
        }

        revisedImageUrl = uriBuilder.toString();
    }

    return revisedImageUrl;
}

    final String newImageUlr = reviseImageUrl(displySize.x, displySize.y, urlSource);

然后:

    Picasso.with(this)
                .load(newImageUlr)
                .resize(size, size)
                .centerInside()
                .into(imageViewd);

编辑:getDisplaySize()

display.getWidth()/getHeight()已经过时。请使用DisplayMetrics而不是Display

public Point getDisplaySize(DisplayMetrics displayMetrics) {
        int width = displayMetrics.widthPixels;
        int height = displayMetrics.heightPixels;
        return new Point(width, height);
}

6

BitmapRegionDecoder 可以解决这个问题。

您可以重写 onDraw(Canvas canvas) 方法,启动新线程并解码用户可见的区域。


5
根据Larcho的指示,从API 10开始,您可以使用BitmapRegionDecoder从图像中加载特定区域,并通过仅在内存中分配所需区域来实现以高分辨率显示大型图像。我最近开发了一个库,提供了触摸手势处理的大型图像可视化。源代码和示例在此处可用

4

视图级别

您可以使用以下代码在运行时禁用单个视图的硬件加速:

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接