2014-02-20 5 views
2

Я пытаюсь написать UIImage как tiff, используя libtiff. Проблема в том, что, хотя я пишу ее как 1 бит на пиксель, файлы все еще выходят в диапазоне 2-5 МБ, когда я ожидаю чего-то более 100 тыс. Или меньше.Как написать 1bpp tiff с libtiff на iOS?

Вот что у меня есть.

- (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 вместо этого. С этим я могу получить то же изображение примерно до 130k как 8-битный PNG. Если я отправлю этот PNG на сайт оптимизатора PNG, они могут получить его примерно до 25k. Если кто-то может показать мне, как писать 1-битный PNG, созданный из моих фильтров GPUImage, я откажусь от tiff.

Спасибо!

ответ

4

Мне нужно создать изображение TIFF в iPhone и отправить его на удаленный сервер, ожидающий файлы TIFF. Я не могу использовать принятый ответ, который преобразуется в PNG 1bpp, и я работал в решении для преобразования в формат TIFF, 1bpp CCITT Group 4, используя libTIFF.

После отладки метода я нашел, где ошибки, и я, наконец, получил правильное решение.

Следующий блок кода - это решение. Прочтите после кода, чтобы найти объяснение ошибок в методе 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_MINISBLACK);

по

TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);

, а затем инвертировать порог сравнения, заменить линию

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

от

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

В моем методе я использую С-арифметикой указателей вместо индекса для доступа к растровому изображению в памяти.

Наконец, несколько улучшений:

а) определить кодировку исходного UIImage (RGBA, ABGR и т.д.) и получить правильные значения RGB для каждого пикселя

б) алгоритм преобразование из изображения в оттенках серого в двухтональное изображение можно улучшить, используя алгоритм адаптивного порога вместо чистого двоичного условного.

+1

Ничего себе, спасибо. Я собираюсь попробовать это завтра. В настоящее время я использую адаптивный пороговый фильтр GPUImage. Он отлично работает, за исключением того, что сплошные черные области изображения переходят в белый цвет. Я вернусь к вам в записи TIFF. – bmauter

+0

@bmauter По правде говоря, я делаю преобразование в би-тональное с помощью адаптированного алгоритма с адаптивным порогом. Я использую OpenCV для управления изображением. Как только у меня есть двухтональное изображение, я использую преобразование tiff перед загрузкой изображения на сервер. Ключевым моментом здесь является преобразование в би-тональное и без алгоритма адаптивного порога, результаты могут быть плохими.Дайте мне знать, работает ли для вас алгоритм TIFF. –

+0

@bmauter Вы попробовали решение TIFF, которое я предложил? –

1

Я закончил работу с GPUImage и libpng. Если кто-то хочет знать, как написать PNG в ИО внешней стороне UIPNGRepresentation, здесь идет:

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

Почему вы хотите это сделать? Представление UIPNGR представляет собой RGBA с 8 бит на компонент. Это 32 бита на пиксель. Так как я хотел получить монохромное изображение 1728x2304, мне нужно всего лишь 1 бит на пиксель, и я получаю изображения размером до 40k. Такое же изображение с представлением UIPNGR составляет 130k. К счастью, сжатие помогает в 32-битной версии, но изменение глубины бит до 1 действительно уменьшает размер файла до очень небольшого размера.

+1

Кстати, разные изображения могут использовать разные байтовые кодировки. Я имею дело только с изображениями, снятыми камерой устройства, поэтому бит всегда был RGBA (8 бит на канал). Заметьте, что я использовал первый байт (красный) и игнорировал остальные три. Если ваше изображение закодировано в ARGB, и вы только читаете первый байт, вы получите только значения альфа-канала. Вероятно, они могут быть только белыми или только черными. Значение alphaInfo указывает, какую кодировку ожидать. Проверьте CGImage.h для получения дополнительной информации. – bmauter

 Смежные вопросы

  • Нет связанных вопросов^_^