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

标签 ios uiimage png gpuimage libtiff

我正在尝试使用 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);

}

第一行 NSLog 说:

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

我还有一个使用 GPUImage 的项目版本。有了它,我可以获得与 8 位 PNG 相同的图像,分辨率降至 130k 左右。如果我将该 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);
}

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

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 的上限才能得到一个 strip 的字节数。例如,一条 83 像素的 strip 需要 11 个字节,而不是 10 个字节,否则我们可能会丢失最后 3 个像素。另请注意,我们必须除以 8.0 才能获得 float 并将其传递给 ceil 函数。 C 中的整数除法会丢失小数部分并四舍五入到底数,这在我们的例子中是错误的。

2) 传递给函数的最后一个参数 TIFFWriteEncodedStrip是错的。我们不能传递 strip 中的像素数,我们必须传递每个 strip 的字节数。

所以替换:

TIFFWriteEncodedStrip(tiff, y, line, width);

通过

TIFFWriteEncodedStrip(tiff, y, line, bytesPerStrip);

3) 最后一个难以检测的错误与黑白图像中0值位表示白色还是黑色的约定有关。感谢 TIFF 标题 TIFFTAG_PHOTOMETRIC我们可以安全地指出这一点。但是我发现一些较旧的软件会忽略此 header 。如果 header 不存在或被忽略,会发生什么 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) 可以通过使用自适应阈值算法而不是纯二值条件算法来改进从灰度图像转换为黑白图像的算法。

关于ios - 如何在 iOS 上使用 libtiff 编写 1bpp tiff?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21916113/

相关文章:

ios - 没有从 UIScrollView 得到接触

html - 如何设置 iOS6 文件输入控件的样式?

ios - 如何将不规则形状存储为 UIImage?

python - .py转.exe,三个问题

ios - Swift - webView 顶部的 iAd 横幅

ios - 从 Firebase 检索信息时控制流如何工作?

ios - 如何为缩略图快速加载图像的低分辨率版本?或者简单地说,如何最好地创建缩略图?

ios - 将 Base64 NSString 转换为 UIImage?

Android:更改png文件颜色的最简单方法

linux - 调整大小不遵守分辨率规范