bma*_*ter 2 png uiimage libtiff ios gpuimage
我正在尝试使用 libtiff 将 UIImage 写为 tiff。问题是,即使我将它写为每像素 1 位,当我期望更接近 100k 或更少的东西时,文件仍然在 2-5MB 范围内。
这就是我所拥有的。
- (void) convertUIImage:(UIImage *)uiImage toTiff:(NSString *)file withThreshold:(float)threshold {
TIFF *tiff;
if ((tiff = TIFFOpen([file UTF8String], "w")) == NULL) {
[[[UIAlertView alloc] initWithTitle:@"Error" message:[NSString stringWithFormat:@"Unable to write to file %@.", file] delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil] show];
return;
}
CGImageRef image = [uiImage CGImage];
CGDataProviderRef provider = CGImageGetDataProvider(image);
CFDataRef pixelData = CGDataProviderCopyData(provider);
unsigned char *buffer = (unsigned char *)CFDataGetBytePtr(pixelData);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image);
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(image);
size_t compBits = CGImageGetBitsPerComponent(image);
size_t pixelBits = CGImageGetBitsPerPixel(image);
size_t width = CGImageGetWidth(image);
size_t height = CGImageGetHeight(image);
NSLog(@"bitmapInfo=%d, alphaInfo=%d, pixelBits=%lu, compBits=%lu, width=%lu, height=%lu", bitmapInfo, alphaInfo, pixelBits, compBits, width, height);
TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, width);
TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, height);
TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 1);
TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 1);
TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, 1);
TIFFSetField(tiff, TIFFTAG_FAXMODE, FAXMODE_CLASSF);
TIFFSetField(tiff, TIFFTAG_COMPRESSION, COMPRESSION_CCITTFAX4);
TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
TIFFSetField(tiff, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB);
TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
TIFFSetField(tiff, TIFFTAG_XRESOLUTION, 200.0);
TIFFSetField(tiff, TIFFTAG_YRESOLUTION, 200.0);
TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);
unsigned char red, green, blue, gray, bite;
unsigned char *line = (unsigned char *)_TIFFmalloc(width/8);
unsigned long pos;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
pos = y * width * 4 + x * 4; // multiplying by four because each pixel is represented by four bytes
red = buffer[ pos ];
green = buffer[ pos + 1 ];
blue = buffer[ pos + 2 ];
gray = .3 * red + .59 * green + .11 * blue; // http://answers.yahoo.com/question/index?qid=20100608031814AAeBHPU
bite = line[x / 8];
bite = bite << 1;
if (gray > threshold) bite = bite | 1;
// NSLog(@"y=%d, x=%d, byte=%d, red=%d, green=%d, blue=%d, gray=%d, before=%@, after=%@", y, x, x/8, red, green, blue, gray, [self bitStringForChar:line[x / 8]], [self bitStringForChar:bite]);
line[x / 8] = bite;
}
TIFFWriteEncodedStrip(tiff, y, line, width);
}
// Close the file and free buffer
TIFFClose(tiff);
if (line) _TIFFfree(line);
if (pixelData) CFRelease(pixelData);
}
Run Code Online (Sandbox Code Playgroud)
第一行 NSLog 说:
bitmapInfo=5, alphaInfo=5, pixelBits=32, compBits=8, width=3264, height=2448
Run Code Online (Sandbox Code Playgroud)
我也有这个项目的一个版本,它使用 GPUImage 代替。有了这个,我可以将相同的图像缩小到 130k 左右,作为 8 位 PNG。如果我将该 PNG 发送到 PNG 优化器站点,他们可以将其降低到大约 25k。如果有人可以告诉我如何编写从我的 GPUImage 过滤器生成的 1 位 PNG,我将放弃 tiff。
谢谢!
我需要在 iPhone 中生成 TIFF 图像并将其发送到需要 TIFF 文件的远程服务器。我无法使用已接受的转换为 1bpp PNG 的答案,我一直在研究使用 libTIFF 转换为 TIFF、1bpp CCITT Group 4 格式的解决方案。
在调试该方法后,我找到了错误所在,最终得到了正确的解决方案。
以下代码块是解决方案。阅读代码以找到对 OP 方法中错误的解释。
- (void) convertUIImage:(UIImage *)uiImage toTiff:(NSString *)file withThreshold:(float)threshold {
CGImageRef srcCGImage = [uiImage CGImage];
CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(srcCGImage));
unsigned char *pixelDataPtr = (unsigned char *)CFDataGetBytePtr(pixelData);
TIFF *tiff;
if ((tiff = TIFFOpen([file UTF8String], "w")) == NULL) {
[[[UIAlertView alloc] initWithTitle:@"Error" message:[NSString stringWithFormat:@"Unable to write to file %@.", file] delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil] show];
return;
}
size_t width = CGImageGetWidth(srcCGImage);
size_t height = CGImageGetHeight(srcCGImage);
TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, width);
TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, height);
TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 1);
TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 1);
TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, 1);
TIFFSetField(tiff, TIFFTAG_COMPRESSION, COMPRESSION_CCITTFAX4);
TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);
TIFFSetField(tiff, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB);
TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
TIFFSetField(tiff, TIFFTAG_XRESOLUTION, 200.0);
TIFFSetField(tiff, TIFFTAG_YRESOLUTION, 200.0);
TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);
unsigned char *ptr = pixelDataPtr; // initialize pointer to the first byte of the image buffer
unsigned char red, green, blue, gray, eightPixels;
tmsize_t bytesPerStrip = ceil(width/8.0);
unsigned char *strip = (unsigned char *)_TIFFmalloc(bytesPerStrip);
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
red = *ptr++; green = *ptr++; blue = *ptr++;
ptr++; // discard fourth byte by advancing the pointer 1 more byte
gray = .3 * red + .59 * green + .11 * blue; // http://answers.yahoo.com/question/index?qid=20100608031814AAeBHPU
eightPixels = strip[x/8];
eightPixels = eightPixels << 1;
if (gray < threshold) eightPixels = eightPixels | 1; // black=1 in tiff image without TIFFTAG_PHOTOMETRIC header
strip[x/8] = eightPixels;
}
TIFFWriteEncodedStrip(tiff, y, strip, bytesPerStrip);
}
TIFFClose(tiff);
if (strip) _TIFFfree(strip);
if (pixelData) CFRelease(pixelData);
}
Run Code Online (Sandbox Code Playgroud)
以下是错误和错误的解释。
1) 如果图像的宽度不是 8 的倍数,则一条扫描线的内存分配为 1 字节短。
unsigned char *line = (unsigned char *)_TIFFmalloc(width/8);
应该替换为
tmsize_t bytesPerStrip = ceil(width/8.0);
unsigned char *line = (unsigned char *)_TIFFmalloc(bytesPerStrip);
解释是我们必须将除法的上限除以 8 才能获得条带的字节数。例如,83 个像素的条需要 11 个字节,而不是 10 个,否则我们可能会丢失最后 3 个像素。另请注意,我们必须除以 8.0 才能获得浮点数并将其传递给 ceil 函数。C 中的整数除法会丢失小数部分并四舍五入到地板,这在我们的例子中是错误的。
2) 传递给函数的最后一个参数TIFFWriteEncodedStrip
是错误的。我们不能传递条带中的像素数,我们必须传递每个条带的字节数。
所以替换:
TIFFWriteEncodedStrip(tiff, y, line, width);
经过
TIFFWriteEncodedStrip(tiff, y, line, bytesPerStrip);
3)最后一个难以检测的错误与二色调图像中一个值为0的位是代表白色还是黑色的约定有关。多亏了 TIFF 标头,TIFFTAG_PHOTOMETRIC
我们可以安全地指出这一点。但是我发现一些较旧的软件忽略了这个标题。如果标头不存在或被忽略,会发生什么情况是0
一位被解释为white
,1
一位被解释为black
.
出于这个原因,我建议更换线路
TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
经过
TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);
然后反转阈值比较,替换线
if (gray > threshold) bite = bite | 1;
经过
if (gray < threshold) bite = bite | 1;
在我的方法中,我使用 C 指针算法而不是索引来访问内存中的位图。
最后,一些改进:
a) 检测原始 UIImage 的编码(RGBA、ABGR 等),得到每个像素的正确 RGB 值
b) 可以通过使用自适应阈值算法而不是纯二进制条件来改进从灰度图像转换为双色调图像的算法。
归档时间: |
|
查看次数: |
1525 次 |
最近记录: |