如何在iOS上用libtiff写一个1bpp tiff? [英] How do I write a 1bpp tiff with libtiff on iOS?

查看:254
本文介绍了如何在iOS上用libtiff写一个1bpp tiff?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用libtiff将UIImage写成tiff。问题是即使我把它写成每像素1位,当我期待更像100k或更少的东西时,文件仍然在2-5MB范围内出现。

I'm trying to write a UIImage out as a tiff using libtiff. The problem is that even though I'm writing it as 1 bit per pixel, the files are still coming out in the 2-5MB range when I'm expecting something more like 100k or less.

这就是我所拥有的。

- (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线说:

The first NSLog line says:

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

我还有一个使用GPUImage的项目版本。有了这个,我可以将相同的图像作为8位PNG得到大约130k。如果我将PNG发送到PNG优化器站点,他们可以将其降低到大约25k。如果有人可以告诉我如何编写从我的GPUImage过滤器生成的1位PNG,我将放弃tiff。

I've also got a version of this project that uses GPUImage instead. With that I can get the same image down to about 130k as an 8-bit PNG. If I send that PNG to a PNG optimizer site, they can get it down to about 25k. If someone can show me how to write a 1 bit PNG generated from my GPUImage filters, I'll forego the tiff.

谢谢!

推荐答案

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

I have the need to generate a TIFF image in the iPhone and send it to a remote server which is expecting TIFF files. I can't use the accepted answer which converts to 1bpp PNG and I have been working in a solution to convert to TIFF, 1bpp CCITT Group 4 format, using libTIFF.

在调试方法之后,我找到了错误的位置,最后得到了正确的解决方案。

After debugging the method I have found where the errors are and I finally got the correct solution.

以下代码块是解决方案。在代码之后读取以找到OP方法中的错误的解释。

The following block of code is the solution. Read after the code to found the explanation to the errors in the OP method.

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

以下是错误和错误说明。

Here are the errors and the explanation of what is wrong.

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

1) the allocation of memory for one scan line is 1 byte short if the width of the image is not a multiple of 8.


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中的整数除法会丢失小数部分并向下舍入,这在我们的情况下是错误的。

The explanation is that we have to take the ceiling of the division by 8 in order to get the number of bytes for a strip. For example a strip of 83 pixels needs 11 bytes, not 10, or we could loose the 3 last pixels. Note also we have to divide by 8.0 in order to get a floating point number and pass it to the ceil function. Integer division in C looses the decimal part and rounds to the floor, which is wrong in our case.

2)传递给函数的最后一个参数 TIFFWriteEncodedStrip 错误。我们无法传递一个条带中的像素数,我们必须传递每个条带的字节数。

2) the last argument passed to the function TIFFWriteEncodedStrip is wrong. We can't pass the number of pixels in a strip, we have to pass the number of bytes per strip.

所以替换:


TIFFWriteEncodedStrip(tiff,y,line,width);

TIFFWriteEncodedStrip(tiff,y,line,bytesPerStrip);

3)难以检测的最后一个错误与关于0值的位是否在双色调图像中表示白色或黑色的约定有关。感谢TIFF标题 TIFFTAG_PHOTOMETRIC ,我们可以安全地指出这一点。但是我发现比一些旧软件忽略了这个标题。如果标头不存在或被忽略,会发生什么? 0 位被解释为 white 1 位被解释为黑色

3) A last error difficult to detect is related to the convention on whether a bit with 0 value represents white or black in the bi-tonal image. Thanks to the TIFF header TIFFTAG_PHOTOMETRIC we can safely indicate this. However I have found than some older software ignores this header. What happens if the header is not present or ignored is that a 0 bit gets interpreted as white and a 1 bit gets interpreted as black.

因此我推荐替换行


TIFFSetField(tiff,TIFFTAG_PHOTOMETRIC,PHOTOMETRIC_MINISBLACK);

by

TIFFSetField(tiff,TIFFTAG_PHOTOMETRIC,PHOTOMETRIC_MINISWHITE );

然后反转阈值比较,替换行

and then invert the threshold comparison, replace line


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

by

if(灰色< threshold)bite = bite | 1;

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

In my method I use C-pointer arithmetic instead of an index to access the bitmap in memory.

最后,进行了一些改进:

Finally, a couple of improvements:

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

a) detect the encoding of the original UIImage (RGBA, ABGR, etc.) and get the correct RGB values for each pixel

b)通过使用自适应阈值算法而不是使用自适应阈值算法,可以改善从灰度图像转换为双色调图像的算法纯二元条件。

b) the algorithm to convert from a grayscale image to a bi-tonal image could be improved by using an adaptive-threshold algorithm instead of a pure binary conditional.

这篇关于如何在iOS上用libtiff写一个1bpp tiff?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆