void SetEncodedRGBValue(Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, unsigned int max, unsigned int red, unsigned int green, unsigned int blue) { if (!img->IsIndexed() && img->GetMaxIntValue() == max && GammaCurve::IsNeutral(g)) // avoid potential re-quantization in case we have a pretty match between encoded data and container img->SetRGBValue(x, y, red, green, blue); else img->SetRGBValue(x, y, IntDecode(g,red,max), IntDecode(g,green,max), IntDecode(g,blue,max)); }
float* GammaCurve::GetLookupTable(unsigned int max) { POV_COLOURSPACE_ASSERT(max == 255 || max == 65535); // shouldn't happen, but it won't hurt to check in debug versions // Get a reference to the lookup table pointer we're dealing with, so we don't need to duplicate all the remaining code. float*& lookupTable = (max == 255 ? lookupTable8 : lookupTable16); #if POV_MULTITHREADED // Make sure we're not racing any other thread that might currently be busy creating the LUT. boost::mutex::scoped_lock lock(lutMutex); #endif // Create the LUT if it doesn't exist yet. if (!lookupTable) { float* tempTable = new float[max+1]; for (unsigned int i = 0; i <= max; i ++) tempTable[i] = Decode(IntDecode(i, max)); // hook up the table only as soon as it is completed, so that querying the table does not need to // care about thread-safety. lookupTable = tempTable; } return lookupTable; }
void SetEncodedGrayAValue(Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, unsigned int max, unsigned int gray, unsigned int alpha, bool premul) { bool doPremultiply = (alpha != max) && !premul && (img->IsPremultiplied() || !img->HasTransparency()); // need to apply premultiplication if encoded data isn't PM'ed but container content should be bool doUnPremultiply = (alpha != max) && premul && !img->IsPremultiplied() && img->HasTransparency(); // need to undo premultiplication if other way round if (!doPremultiply && !doUnPremultiply && !img->IsIndexed() && img->GetMaxIntValue() == max && GammaCurve::IsNeutral(g)) // avoid potential re-quantization in case we have a pretty match between encoded data and container img->SetGrayAValue(x, y, gray, alpha); else { float fAlpha = IntDecode(alpha,max); float fGray = IntDecode(g,gray,max); if (doPremultiply) AlphaPremultiply(fGray, fAlpha); else if (doUnPremultiply) AlphaUnPremultiply(fGray, fAlpha); // else no need to worry about premultiplication img->SetGrayAValue(x, y, fGray, fAlpha); } }
Image *Read (IStream *file, const Image::ReadOptions& options) { int nrow; int result = 0; long LineSize; TIFF *tif; Image *image ; uint16 BitsPerSample; uint16 BytesPerSample = 1; uint16 PhotometricInterpretation; uint16 SamplePerPixel; uint16 Orientation; uint32 RowsPerStrip; unsigned int width; unsigned int height; // TODO - TIFF files probably have some gamma info in them by default, but we're currently ignorant about that. // Until that is fixed, use whatever the user has chosen as default. GammaCurvePtr gamma; if (options.gammacorrect && options.defaultGamma) gamma = TranscodingGammaCurve::Get(options.workingGamma, options.defaultGamma); // [CLi] TIFF is specified to use associated (= premultiplied) alpha, so that's the preferred mode to use for the image container unless the user overrides // (e.g. to handle a non-compliant file). bool premul = true; if (options.premultiplyOverride) premul = options.premultiply; // Rather than have libTIFF complain about tags it doesn't understand, // we just suppress all the warnings. TIFFSetWarningHandler(SuppressTIFFWarnings); TIFFSetErrorHandler(SuppressTIFFWarnings); // Open and do initial processing tif = TIFFClientOpen("Dummy File Name", "r", file, Tiff_Read, Tiff_Write, Tiff_Seek, Tiff_Close, Tiff_Size, Tiff_Map, Tiff_Unmap); if (!tif) return (NULL) ; // Get basic information about the image int ExtraSamples, ExtraSampleInfo; TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width); TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height); TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &BitsPerSample); TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &RowsPerStrip); TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &PhotometricInterpretation); TIFFGetField(tif, TIFFTAG_ORIENTATION, &Orientation); TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &SamplePerPixel); TIFFGetFieldDefaulted(tif, TIFFTAG_EXTRASAMPLES, &ExtraSamples, &ExtraSampleInfo); // don't support more than 16 bits per sample if (BitsPerSample == 16) { BytesPerSample = 2 ; options.warnings.push_back ("Warning: reading 16 bits/sample TIFF file; components crunched to 8"); } LineSize = TIFFScanlineSize(tif); assert (SamplePerPixel == (int) (LineSize / width) / BytesPerSample); // SamplePerPixel = (int)(LineSize / width); #if 0 // For now we are ignoring the orientation of the image... switch (Orientation) { case ORIENTATION_TOPLEFT: break; case ORIENTATION_TOPRIGHT: break; case ORIENTATION_BOTRIGHT: break; case ORIENTATION_BOTLEFT: break; case ORIENTATION_LEFTTOP: break; case ORIENTATION_RIGHTTOP: break; case ORIENTATION_RIGHTBOT: break; case ORIENTATION_LEFTBOT: break; default: break; } #endif //PhotometricInterpretation = 2 image is RGB //PhotometricInterpretation = 3 image have a color palette if (PhotometricInterpretation == PHOTOMETRIC_PALETTE && (TIFFIsTiled(tif) == 0)) { uint16 *red, *green, *blue; //load the palette int cmap_len = (1 << BitsPerSample); TIFFGetField(tif, TIFFTAG_COLORMAP, &red, &green, &blue); vector<Image::RGBMapEntry> colormap ; Image::RGBMapEntry entry; // I may be mistaken, but it appears that alpha/opacity information doesn't // appear in a Paletted Tiff image. Well - if it does, it's not as easy to // get at as RGB. // Read the palette // Is the palette 16 or 8 bits ? if (checkcmap(cmap_len, red, green, blue) == 16) { for (int i=0;i<cmap_len;i++) { entry.red = IntDecode(gamma, red[i], 65535); entry.green = IntDecode(gamma, green[i], 65535); entry.blue = IntDecode(gamma, blue[i], 65535); colormap.push_back (entry); } } else { for (int i=0;i<cmap_len;i++) { entry.red = IntDecode(gamma, red[i], 255); entry.green = IntDecode(gamma, green[i], 255); entry.blue = IntDecode(gamma, blue[i], 255); colormap.push_back (entry); } } Image::ImageDataType imagetype = options.itype; if (imagetype == Image::Undefined) imagetype = Image::Colour_Map; image = Image::Create (width, height, imagetype, colormap) ; image->SetPremultiplied(premul); // specify whether the color map data has premultiplied alpha boost::scoped_array<unsigned char> buf (new unsigned char [TIFFStripSize(tif)]); //read the tiff lines and save them in the image //with RGB mode, we have to change the order of the 3 samples RGB <=> BGR for (int row=0;row<height;row+=RowsPerStrip) { nrow = (row + (int)RowsPerStrip > height ? height - row : RowsPerStrip); TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, row, 0), buf.get(), nrow * LineSize); for (int l = 0, offset = 0; l < nrow ; l++, offset += LineSize) for (int x = 0 ; x < width ; x++) image->SetIndexedValue (x, row+l, buf[offset+x]) ; } } else { // Allocate the row buffers for the image boost::scoped_array<uint32> buf (new uint32 [width * height]) ; Image::ImageDataType imagetype = options.itype; if (imagetype == Image::Undefined) imagetype = ( GammaCurve::IsNeutral(gamma) ? Image::RGBA_Int8 : Image::RGBA_Gamma8 ); image = Image::Create (width, height, imagetype) ; image->SetPremultiplied(premul); // set desired storage mode regarding alpha premultiplication image->TryDeferDecoding(gamma, 255); // try to have gamma adjustment being deferred until image evaluation. TIFFReadRGBAImage(tif, width, height, buf.get(), 0); uint32 abgr, *tbuf = buf.get(); for (int i=height-1;i>=0;i--) { for (int j=0;j<width;j++) { abgr = *tbuf++; unsigned int b = (unsigned char)TIFFGetB(abgr); unsigned int g = (unsigned char)TIFFGetG(abgr); unsigned int r = (unsigned char)TIFFGetR(abgr); unsigned int a = (unsigned char)TIFFGetA(abgr); SetEncodedRGBAValue(image, j, i, gamma, 255, r, g, b, a, premul) ; } } } TIFFClose(tif); return (image) ; }
// TODO: make sure we don't leak an image object if we throw an exception. Image *Read (IStream *file, const Image::ReadOptions& options, bool IsPOTFile) { int data ; int width; int height; Image *image ; unsigned char buffer[256]; vector<Image::RGBAMapEntry> colormap ; int alphaIdx = -1; // assume no transparency color // GIF files used to have no clearly defined gamma by default, but a W3C recommendation exists for them to use sRGB. // Anyway, use whatever the user has chosen as default. GammaCurvePtr gamma; if (options.gammacorrect) { if (options.defaultGamma) gamma = TranscodingGammaCurve::Get(options.workingGamma, options.defaultGamma); else gamma = TranscodingGammaCurve::Get(options.workingGamma, SRGBGammaCurve::Get()); } int status = 0; /* Get the screen description. */ if (!file->read (buffer, 13)) throw POV_EXCEPTION(kFileDataErr, "Cannot read GIF file header"); /* Use updated GIF specs. */ if (memcmp ((char *) buffer, "GIF", 3) != 0) throw POV_EXCEPTION(kFileDataErr, "File is not in GIF format"); if (buffer[3] != '8' || (buffer[4] != '7' && buffer[4] != '9') || buffer[5] < 'A' || buffer[5] > 'z') throw POV_EXCEPTION(kFileDataErr, "Unsupported GIF version"); int planes = ((unsigned) buffer [10] & 0x0F) + 1; int colourmap_size = (1 << planes); /* Color map (better be!) */ if ((buffer[10] & 0x80) == 0) throw POV_EXCEPTION(kFileDataErr, "Error in GIF color map"); for (int i = 0; i < colourmap_size ; i++) { Image::RGBAMapEntry entry; if (!file->read (buffer, 3)) throw POV_EXCEPTION(kFileDataErr, "Cannot read GIF colormap"); entry.red = IntDecode(gamma, buffer[0], 255); entry.green = IntDecode(gamma, buffer[1], 255); entry.blue = IntDecode(gamma, buffer[2], 255); entry.alpha = 1.0f; colormap.push_back(entry); } /* Now read one or more GIF objects. */ bool finished = false; while (!finished) { switch (file->Read_Byte()) { case EOF: throw POV_EXCEPTION(kFileDataErr, "Unexpected EOF reading GIF file"); finished = true; break ; case ';': /* End of the GIF dataset. */ finished = true; status = 0; break; case '!': /* GIF Extension Block. */ /* Read (and check) the ID. */ if (file->Read_Byte() == 0xF9) { if ((data = file->Read_Byte()) > 0) { if (!file->read (buffer, data)) throw POV_EXCEPTION(kFileDataErr, "Unexpected EOF reading GIF file"); // check transparency flag, and set transparency color index if appropriate if (data >= 3 && buffer[0] & 0x01) { int alphaIdx = buffer[3]; if (alphaIdx < colourmap_size) colormap[alphaIdx].alpha = 0.0f; } } else break; } while ((data = file->Read_Byte()) > 0) if (!file->read (buffer, data)) throw POV_EXCEPTION(kFileDataErr, "Unexpected EOF reading GIF file"); break; case ',': /* Start of image object. Get description. */ for (int i = 0; i < 9; i++) { if ((data = file->Read_Byte()) == EOF) throw POV_EXCEPTION(kFileDataErr, "Unexpected EOF reading GIF file"); buffer[i] = (unsigned char) data; } /* Check "interlaced" bit. */ if ((buffer[9] & 0x40) != 0) throw POV_EXCEPTION(kFileDataErr, "Interlacing in GIF image unsupported"); width = (int) buffer[4] | ((int) buffer[5] << 8); height = (int) buffer[6] | ((int) buffer[7] << 8); image = Image::Create (width, height, Image::Colour_Map, colormap) ; // [CLi] GIF only uses full opacity or full transparency, so premultiplied vs. non-premultiplied alpha is not an issue /* Get bytes */ Decode (file, image); finished = true; break; default: status = -1; finished = true; break; } } if (IsPOTFile == false) { if (!image) throw POV_EXCEPTION(kFileDataErr, "Cannot find GIF image data block"); return (image); } // POT files are GIF files where the right half of the image contains // a second byte for each pixel on the left, thus allowing 16-bit // indexes. In this case the palette data is ignored and we convert // the image into a 16-bit grayscale version. if ((width & 0x01) != 0) throw POV_EXCEPTION(kFileDataErr, "Invalid width for POT file"); int newWidth = width / 2 ; Image *newImage = Image::Create (newWidth, height, Image::Gray_Int16) ; for (int y = 0 ; y < height ; y++) for (int x = 0 ; x < newWidth ; x++) newImage->SetGrayValue (x, y, (unsigned int) image->GetIndexedValue (x, y) << 8 | image->GetIndexedValue (x + newWidth, y)) ; // NB: POT files don't use alpha, so premultiplied vs. non-premultiplied is not an issue // NB: No gamma adjustment happening here! delete image ; return (newImage) ; }