这不是一个简单的问题,请通读!
我想操作一个 JPEG 文件并将其再次保存为 JPEG。问题是,即使没有操作,也会有显着的(可见的)质量损失。
问题 :我缺少什么选项或 API 才能在没有质量损失的情况下重新压缩 JPEG(我知道这不太可能,但我认为我在下面描述的不是可接受的伪像水平,尤其是质量 = 100)。
控制
我将它加载为 Bitmap从文件:
BitmapFactory.Options options = new BitmapFactory.Options();
// explicitly state everything so the configuration is clear
options.inPreferredConfig = Config.ARGB_8888;
options.inDither = false; // shouldn't be used anyway since 8888 can store HQ pixels
options.inScaled = false;
options.inPremultiplied = false; // no alpha, but disable explicitly
options.inSampleSize = 1; // make sure pixels are 1:1
options.inPreferQualityOverSpeed = true; // doesn't make a difference
// I'm loading the highest possible quality without any scaling/sizing/manipulation
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/image.jpg", options);
bitmap.compress(PNG, 100/*ignored*/, new FileOutputStream("/sdcard/image.png"));
int[]来自 getPixels并将其作为原始 ARGB 文件加载到我的计算机上:与原始 JPEG 和从位图保存的 PNG 没有视觉差异。ARGB_8888正如预期的那样。// 100% still expected lossy, but not this amount of artifacts
bitmap.compress(JPEG, 100, new FileOutputStream("/sdcard/image.jpg"));






byte[]并且是一个编码的 JPEG blob。我通过 OutputStream.write(byte[]) 保存了这个文件方法,那是我的原始源文件。 decodeByteArray(data, 0, data.length, options)解码与从文件中读取相同的像素,使用 Bitmap.sameAs 进行测试所以这与问题无关。最佳答案
经过一番调查,我找到了罪魁祸首:Skia 的 YCbCr 转换。可在 TWiStErRob/AndroidJPEG 中找到 Repro、调查代码和解决方案.
发现
在没有得到对这个问题的积极回应(都不是来自 http://b.android.com/206128 )之后,我开始深入挖掘。我发现了许多半知半解的 SO 答案,这极大地帮助了我发现点点滴滴。一个这样的答案是 https://stackoverflow.com/a/13055615/253468这让我知道 YuvImage它将 YUV NV21 字节数组转换为 JPEG 压缩字节数组:
YuvImage yuv = new YuvImage(yuvData, ImageFormat.NV21, width, height, null);
yuv.compressToJpeg(new Rect(0, 0, width, height), 100, jpeg);
YuvImage在调用 Bitmap.compress 时实际上并没有使用, 这是 Bitmap.compress 的堆栈:jpeg_write_scanlines ( jcapistd.c:77 ) rgb2yuv_32 ( SkImageDecoder_libjpeg.cpp:913 ) writer(=Write_32_YUV).write ( SkImageDecoder_libjpeg.cpp:961 )WE_CONVERT_TO_YUV 是无条件定义的] SkJPEGImageEncoder::onEncode ( SkImageDecoder_libjpeg.cpp:1046 ) SkImageEncoder::encodeStream ( SkImageEncoder.cpp:15 ) Bitmap_compress ( Bitmap.cpp:383 ) Bitmap.nativeCompress ( Bitmap.java:1573 ) Bitmap.compress ( Bitmap.java:984 ) app.saveBitmapAsJPEG () YuvImagejpeg_write_raw_data ( jcapistd.c:120 ) YuvToJpegEncoder::compress ( YuvToJpegEncoder.cpp:71 ) YuvToJpegEncoder::encode ( YuvToJpegEncoder.cpp:24 ) YuvImage_compressToJpeg ( YuvToJpegEncoder.cpp:219 ) YuvImage.nativeCompressToJpeg ( YuvImage.java:141 ) YuvImage.compressToJpeg ( YuvImage.java:123 ) app.saveNV21AsJPEG () rgb2yuv_32 中的常量来自 Bitmap.compress我能够使用 YuvImage 重新创建相同的 strip 效果,不是成就,只是确认确实是YUV转换搞砸了。我仔细检查了问题不在 YuvImage 期间调用 libjpeg :通过将位图的 ARGB 转换为 YUV 并返回到 RGB,然后将生成的像素 blob 作为原始图像转储, strip 已经存在。rgb_ycc_convert函数来自 jcolor.c并使用来自 https://stackoverflow.com/a/13055615/253468 的骨架用 Java 重写它.下面没有针对速度进行优化,而是针对可读性进行了优化,为简洁起见,删除了一些常量,您可以在 libjpeg 代码或我的示例项目中找到它们。private static final int JSAMPLE_SIZE = 255 + 1;
private static final int CENTERJSAMPLE = 128;
private static final int SCALEBITS = 16;
private static final int CBCR_OFFSET = CENTERJSAMPLE << SCALEBITS;
private static final int ONE_HALF = 1 << (SCALEBITS - 1);
private static final int[] rgb_ycc_tab = new int[TABLE_SIZE];
static { // rgb_ycc_start
for (int i = 0; i <= JSAMPLE_SIZE; i++) {
rgb_ycc_tab[R_Y_OFFSET + i] = FIX(0.299) * i;
rgb_ycc_tab[G_Y_OFFSET + i] = FIX(0.587) * i;
rgb_ycc_tab[B_Y_OFFSET + i] = FIX(0.114) * i + ONE_HALF;
rgb_ycc_tab[R_CB_OFFSET + i] = -FIX(0.168735892) * i;
rgb_ycc_tab[G_CB_OFFSET + i] = -FIX(0.331264108) * i;
rgb_ycc_tab[B_CB_OFFSET + i] = FIX(0.5) * i + CBCR_OFFSET + ONE_HALF - 1;
rgb_ycc_tab[R_CR_OFFSET + i] = FIX(0.5) * i + CBCR_OFFSET + ONE_HALF - 1;
rgb_ycc_tab[G_CR_OFFSET + i] = -FIX(0.418687589) * i;
rgb_ycc_tab[B_CR_OFFSET + i] = -FIX(0.081312411) * i;
}
}
static void rgb_ycc_convert(int[] argb, int width, int height, byte[] ycc) {
int[] tab = LibJPEG.rgb_ycc_tab;
final int frameSize = width * height;
int yIndex = 0;
int uvIndex = frameSize;
int index = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int r = (argb[index] & 0x00ff0000) >> 16;
int g = (argb[index] & 0x0000ff00) >> 8;
int b = (argb[index] & 0x000000ff) >> 0;
byte Y = (byte)((tab[r + R_Y_OFFSET] + tab[g + G_Y_OFFSET] + tab[b + B_Y_OFFSET]) >> SCALEBITS);
byte Cb = (byte)((tab[r + R_CB_OFFSET] + tab[g + G_CB_OFFSET] + tab[b + B_CB_OFFSET]) >> SCALEBITS);
byte Cr = (byte)((tab[r + R_CR_OFFSET] + tab[g + G_CR_OFFSET] + tab[b + B_CR_OFFSET]) >> SCALEBITS);
ycc[yIndex++] = Y;
if (y % 2 == 0 && index % 2 == 0) {
ycc[uvIndex++] = Cr;
ycc[uvIndex++] = Cb;
}
index++;
}
}
}
static byte[] compress(Bitmap bitmap) {
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int[] argb = new int[w * h];
bitmap.getPixels(argb, 0, w, 0, 0, w, h);
byte[] ycc = new byte[w * h * 3 / 2];
rgb_ycc_convert(argb, w, h, ycc);
argb = null; // let GC do its job
ByteArrayOutputStream jpeg = new ByteArrayOutputStream();
YuvImage yuvImage = new YuvImage(ycc, ImageFormat.NV21, w, h, null);
yuvImage.compressToJpeg(new Rect(0, 0, w, h), quality, jpeg);
return jpeg.toByteArray();
}
ONE_HALF - 1其余的看起来很像 Skia 中的数学。这是 future 调查的一个很好的方向,但对我来说,上面的内容足够简单,可以成为解决 Android 内置奇怪问题的一个很好的解决方案,尽管速度较慢。 请注意,此解决方案使用 NV21 布局,该布局丢失了 3/4 的颜色信息(来自 Cr/Cb),但此损失远小于 Skia 数学产生的错误。 另请注意 YuvImage不支持奇数大小的图像,有关更多信息,请参阅 NV21 format and odd image dimensions .
关于android - 如何在Android上以最少的质量损失将位图压缩为JPEG?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36487971/
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby中使用两个参数异步运行exe吗?我已经尝试过ruby命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何rubygems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"
我实际上是在尝试使用RVM在我的OSX10.7.5上更新ruby,并在输入以下命令后:rvminstallruby我得到了以下回复:Searchingforbinaryrubies,thismighttakesometime.Checkingrequirementsforosx.Installingrequirementsforosx.Updatingsystem.......Errorrunning'requirements_osx_brew_update_systemruby-2.0.0-p247',pleaseread/Users/username/.rvm/log/138121
这可能是个愚蠢的问题。但是,我是一个新手......你怎么能在交互式rubyshell中有多行代码?好像你只能有一条长线。按回车键运行代码。无论如何我可以在不运行代码的情况下跳到下一行吗?再次抱歉,如果这是一个愚蠢的问题。谢谢。 最佳答案 这是一个例子:2.1.2:053>a=1=>12.1.2:054>b=2=>22.1.2:055>a+b=>32.1.2:056>ifa>b#Thecode‘if..."startsthedefinitionoftheconditionalstatement.2.1.2:057?>puts"f
我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R