2014-02-20 64 views
2

我試圖用libtiff寫一個UIImage作爲tiff。問題是,儘管我將它寫爲每像素1位,但當我預計更像100k或更少的內容時,這些文件仍在2-5MB範圍內出現。如何在iOS上使用libtiff編寫1bpp tiff?

這是我得到的。

- (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代替。通過這個,我可以將相同的圖像下載到大約13萬像素的8位PNG。如果我將該PNG發送到PNG優化器網站,他們可以將其降至25k左右。如果有人能告訴我如何編寫從我的GPUImage過濾器生成的1位PNG,我會放棄tiff。

謝謝!

回答

4

我有需要在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)的存儲器,用於一條掃描線的分配是1個字節短,如果圖像的寬度不是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中的整數除法小數部分丟到地面上,這在我們的例子中是錯誤的。

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_MINISWHITE);

更換線

TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);

,然後反轉閾值比較,更換線

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

通過

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

在我的方法我使用C-指針算法而不是索引來訪問存儲器中的位圖。

最後,一對夫婦的改進:

一個)檢測原稿的UIImage(RGBA,ABGR等的編碼),並得到正確的RGB值對於每個像素

b)該算法從灰度圖像轉換爲雙色調圖像可以通過使用自適應閾值算法而不是純二進制條件來改進。

+1

哇,謝謝。明天我會試試這個。我目前使用GPUImage的自適應閾值過濾器。它的工作正常,但圖像的純黑色區域變爲白色。我會在TIFF上回復你。 – bmauter

+0

@bmauter事實上,我用定製的自適應閾值算法進行雙色調轉換。我使用OpenCV來操縱圖像。一旦我有了雙色調圖像,我在將圖像上傳到服務器之前使用tiff轉換。這裏的關鍵是轉換爲雙色調,並且沒有自適應閾值算法,結果可能很差。讓我知道TIFF算法是否適合您。 –

+0

@bmauter您是否嘗試過我建議的TIFF解決方案? –

1

我結束了與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位。由於我想要一張單色的1728x2304圖像,因此我只需要每像素1位,而最終的圖像只有40k。與UIPNGRepresentation相同的圖像是130k。值得慶幸的是,壓縮技術可以幫助我們實現32位版本,但將位深度更改爲1可以將其降低到非常小的文件大小。

+1

順便說一句,不同的圖像可以使用不同的字節編碼。我只處理設備攝像頭拍攝的圖像,所以這些位總是RGBA(每通道8位)。注意我正在使用第一個字節(紅色)而忽略其他三個字節。如果您的圖像使用ARGB編碼,並且只讀取第一個字節,則只能獲取Alpha通道值。他們可能只有白人或只有黑人。 alphaInfo值告訴你預期的編碼。有關詳細信息,請查看CGImage.h。 – bmauter

相關問題