Android - 如何在以编程方式加载或保存 JPEG 文件时获取或设置(打印)DPI(每英寸点数)?

Posted

技术标签:

【中文标题】Android - 如何在以编程方式加载或保存 JPEG 文件时获取或设置(打印)DPI(每英寸点数)?【英文标题】:Android - how to get or set (print) DPI ( dots per inch ) of JPEG file while loading or saving it programmatically? 【发布时间】:2014-03-19 16:53:52 【问题描述】:

我有一个在 android 4.0 及更高版本上开发的应用程序。 (该应用不支持4.0以下的Android版本[Ice Cream Sandwich])。

该问题与各种图像(例如 jpeg 或 png )格式的(打印)DPI 有关。

此问题与屏幕 DPI 或各种 Android 设备的尺寸无关。它也与在设备上以屏幕大小显示位图无关。

我正在使用以下代码在“位图”中加载图像文件。然后我一直在裁剪它并使用 jpegCompression 将其保存到另一个 JPEG 格式的文件中。我已经可以通过以下代码做到这一点,但是我无法获取加载的 DPI 或设置保存的图像文件的 DPI。

所以我有两个问题。

1) 如何从 JPEG 文件中获取(打印)DPI,在将其加载到“位图”之后或同时?

2) 在保存新生成的“位图”时,如何在 JPEG 文件中重新设置 DPI?

以下是部分代码供参考。

    FileInputStream inputStream = new FileInputStream(theSourcePhotoFilePathName);

    Bitmap bitmap = null;
    BitmapRegionDecoder decoder = null;

    BitmapFactory.Options options = new BitmapFactory.Options();        
    options.inSampleSize = 1;
    options.inDensity = 300;   // Tried this but not working.

    try 
        decoder = BitmapRegionDecoder.newInstance(in, false);
        bitmap = decoder.decodeRegion(region, options);      // the region has cropping coordinates.
     catch (IllegalArgumentException e)
        Log.d("First Activity", "Failed to recycle bitmap for rect=" + region, e);
     catch (IOException e) 
        Log.d("First Activity", "Failed to decode into rect=" + region, e);
     finally 
        if (decoder != null) decoder.recycle();
    

    inputStream.close();
    inputStream = null;

    FileOutputStream fos = new FileOutputStream( theTargetTempFolderDestFilePath );
    bitmap.compress(CompressFormat.JPEG, jpegCompressionRatio, fos);
    fos.flush();
    fos.close();
    fos = null;

我试图通过谷歌搜索从 *** 和其他网站中找到,但无法获得正确的相关答案。所以我决定在这个论坛问它。

欢迎您的提示和建议。

桑杰。

【问题讨论】:

【参考方案1】:

为了方便这里准备了复制粘贴方法:

public static void saveBitmapToJpg(Bitmap bitmap, File file, int dpi) throws IOException 
    ByteArrayOutputStream imageByteArray = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, imageByteArray);
    byte[] imageData = imageByteArray.toByteArray();

    setDpi(imageData, dpi);

    FileOutputStream fileOutputStream = new FileOutputStream(file);
    fileOutputStream.write(imageData);
    fileOutputStream.close();


private static void setDpi(byte[] imageData, int dpi) 
    imageData[13] = 1;
    imageData[14] = (byte) (dpi >> 8);
    imageData[15] = (byte) (dpi & 0xff);
    imageData[16] = (byte) (dpi >> 8);
    imageData[17] = (byte) (dpi & 0xff);

保存的文件将正确设置图像 DPI 值:

【讨论】:

Gr8 - 为新的 android 开发人员简化解决方案。 这适用于JPEG文件格式,但有人知道PNG文件吗?【参考方案2】:

您指的是作为 JPEG 文件的一部分写入的 DPI 元数据吗?我最近一直在努力解决这个问题,但想出了一个解决方案。如果你已经解决了,这个答案可以帮助遇到同样问题的其他人。

在 Android 中将位图压缩为 JPEG 时,会将其保存为 JFIF 段格式。请在此处查看文章(http://en.wikipedia.org/wiki/JPEG_File_Interchange_Format)。我还附上了在十六进制编辑器中打开的 Android jpeg 图像的屏幕截图,以便您查看它是如何匹配的。

要编辑该值,您需要首先创建一个 byte[] 数组来存储 Bitmap.compress()。这是我的代码的一部分,我就是这样做的(输入是源位图)。

ByteArrayOutputStream uploadImageByteArray = new ByteArrayOutputStream();
input.compress(Bitmap.CompressFormat.JPEG, 100, uploadImageByteArray);
byte[] uploadImageData = uploadImageByteArray.toByteArray();

基于JFIF结构,需要编辑字节数组中的第13、14、15、16、17个索引。 13th 指定密度类型,14th 和 15th 指定 X 分辨率,16th 和 17th 指定 Y 分辨率。就我而言,我将其更改为 500 dpi 作为元数据中的 X 和 Y 分辨率。这转换为 0000 0001 1111 0100,即十六进制的 1F4。然后我将 byte[] 写入文件,将其从手机复制到计算机,并在查看图像属性时验证了详细信息。

                uploadImageData[13] = 00000001;
                uploadImageData[14] = 00000001;
                uploadImageData[15] = (byte) 244
                uploadImageData[16] =  00000001;
                uploadImageData[17] = (byte) 244


                 File image = new  File(Environment
                .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                .getAbsolutePath() + "/meta Images/", imageFileName);
                FileOutputStream sdCardOutput = new FileOutputStream(image);
                sdCardOutput.write(uploadImageData);

注意: Java 使用有符号字节系统,如果没有编译器对您咆哮,您无法输入任何高于 127 的二进制值。我在输入 F4 作为字节时遇到了这个问题。解决方案是将您的值转换为十进制,然后使用(字节)强制转换。

希望这会有所帮助!

【讨论】:

Gr8,我可以通过使用第三方库 sanselanandroid 来做到这一点。但是这种方法更好更快。这种方法改变了 Exif 信息中的数据。通过这种方法,我们能够更改 jpeg 的“X 分辨率”和“Y 分辨率”,其中图像没有缩略图。当我尝试使用照片(取自专业相机,具有缩略图和更多 exif 信息)时,我无法直接设置 DPI 以及 Sanselan 方式。【参考方案3】:

给出的答案是关于如何设置 dpi。为了获得图像的dpi:


   final ImageInfo imageInfo = Sanselan.getImageInfo(image_file);

   final int physicalWidthDpi = imageInfo.getPhysicalWidthDpi();
   final int physicalHeightDpi = imageInfo.getPhysicalHeightDpi();

gradle 依赖:implementation group: 'org.apache.sanselan', name: 'sanselan', version: '0.97-incubator'

【讨论】:

感谢@Talha,展示了使用 Sanselan 获取 dpi 的逻辑。

以上是关于Android - 如何在以编程方式加载或保存 JPEG 文件时获取或设置(打印)DPI(每英寸点数)?的主要内容,如果未能解决你的问题,请参考以下文章

iOS7 UITabBarController 在以编程方式更改 selectedViewController 或 selectedIndex 后没有响应

如何在以编程方式制作滚动视图时解决此问题?

如何在以正确方式获取的事务之外初始化延迟加载的集合?

如何在以编程方式创建实体时设置 NSManagedObjectModel 的配置。

如何使用 XCTest 在以编程方式添加的 UIView 上测试 UILabel

IOS5 - 如何在以编程方式创建的视图中播放视频?