如何在iOS上用libtiff编写1bpp tiff?

我试图用libtiff写一个UIImage作为tiff。 问题是,即使我把它写成每像素1位,当我期望更多的像100k或更less的东西时,文件仍然在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); } 

第一个NSLog行说:

 bitmapInfo=5, alphaInfo=5, pixelBits=32, compBits=8, width=3264, height=2448 

我也有一个使用GPUImage的项目版本。 这样我就可以像8位PNG一样将图像下载到大约130k。 如果我将这个PNG发送到一个PNG优化器网站,他们可以把它降低到25K左右。 如果有人能告诉我如何编写从我的GPUImagefilter生成的1位PNG,我会放弃tiff。

谢谢!

我有需要在iPhone中生成一个TIFF图像,并将其发送到预计TIFF文件的远程服务器。 我不能使用接受的答案转换为1bpp PNG,我一直在使用libTIFF转换为TIFF,1bpp CCITT Group 4格式的解决scheme。

debugging后,我发现错误的方法,我终于得到正确的解决scheme。

以下代码块是解决scheme。 读取代码后,findOP方法中的错误的解释。

 - (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); } 

这里是错误和错误的解释。

1)如果图像的宽度不是8的倍数,则一个扫描行的存储器分配是1个字节的短。

unsigned char *line = (unsigned char *)_TIFFmalloc(width/8);

应该被replace

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的最后一个参数是错误的。 我们无法传递条带中的像素数量,我们必须传递每个条带的字节数。

所以replace:

TIFFWriteEncodedStrip(tiff, y, line, width);

通过

TIFFWriteEncodedStrip(tiff, y, line, bytesPerStrip);

3)最后一个难以检测到的错误与约定是否有0值表示白色或黑色的黑白图像有关。 由于TIFF header TIFFTAG_PHOTOMETRIC我们可以安全地指出这一点。 不过,我发现比一些旧的软件忽略这个头。 如果标题不存在或被忽略,会发生什么情况是0位被解释为white1位被解释为black

为此,我build议更换这一行

TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);

通过

TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);

然后反转阈值比较,replace行

if (gray > threshold) bite = bite | 1;

通过

if (gray < threshold) bite = bite | 1;

在我的方法中,我使用C指针algorithm而不是索引访问内存中的位图。

最后,还有一些改进:

a)检测原始UIImage(RGBA,ABGR等)的编码并获得每个像素的正确的RGB值

b)通过使用自适应门限algorithm而不是纯粹的二进制条件,可以改进从灰度图像到双色调图像的algorithm。

我结束了与GPUImage和libpng。 如果有人想知道如何在UIPNGRepresentation之外的iOS中编写一个png,这里是:

 - (void) writeUIImage:(UIImage *)uiImage toPNG:(NSString *)file { FILE *fp = fopen([file UTF8String], "wb"); if (!fp) return [self reportError:[NSString stringWithFormat:@"Unable to open file %@", file]]; 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); png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) [self reportError:@"Unable to create write struct."]; png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr, (png_infopp)NULL); return [self reportError:@"Unable to create info struct."]; } if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); fclose(fp); return [self reportError:@"Got error callback."]; } png_init_io(png_ptr, fp); png_set_IHDR(png_ptr, info_ptr, (png_uint_32)width, (png_uint_32)height, 1, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_write_info(png_ptr, info_ptr); png_set_packing(png_ptr); png_bytep line = (png_bytep)png_malloc(png_ptr, width); 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 line[x] = buffer[ pos ]; // just use the first byte (red) since r=g=b in grayscale } png_write_row(png_ptr, line); } png_write_end(png_ptr, info_ptr); png_destroy_write_struct(&png_ptr, &info_ptr); if (pixelData) CFRelease(pixelData); fclose(fp); } 

你为什么想做这个? UIPNGRepresentation是每个组件8位的RGBA。 这是每像素32位。 因为我想要一个单色的1728×2304图像,所以我只需要每像素1位,而最终的图像只有40k。 与UIPNGRepresentation相同的图像是130k。 值得庆幸的是,压缩有助于32位版本,但将位深度更改为1会使文件尺寸缩小。