#include "imageheaders.h" #include "imageutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../misc/exif.h" // This is pretty simple. See if there is the required ID's for animated GIFs // in the first 1024 bytes: either NETSCAPE or ANIMEXTS. The exact placement // can differ but should always be in the first 1K, (at least it is in all // my images ;-) bool isAnimatedGIF(const char *fn) { static char buffer[1024]; int fd = open(fn, O_RDONLY); int i, count; if(fd == -1){ qWarning("Could not open GIF to check animation!"); return(false); } count = read(fd, buffer, 1024); close(fd); for(i=0; i < count; ++i){ if(buffer[i] == 'N'){ if(qstrncmp(buffer+i, "NETSCAPE", 8) == 0) return(true); } else if(buffer[i] == 'A'){ if(qstrncmp(buffer+i, "ANIMEXTS", 8) == 0) return(true); } } return(false); } // Checks for TIFF thumbnails. TIFF uses virtual folders inside the image. // The one that has a preview should have a specific tag set but lots of // camera vendors use nonstandard tags. So we look if there is an image // smaller than the main image, and use that if it exists. bool checkTIFFThumbnail(const char *filename, QImage &img) { img.reset(); TIFF *t = TIFFOpen(filename, "r"); if(!t) return(false); int isValidDir = true; uint32 width, height; uint32 maxWidth=0; uint32 minWidth=0; tdir_t thumbDir = (tdir_t)-1; bool sizeProcessed = false; while(isValidDir){ if(TIFFGetField(t, TIFFTAG_IMAGEWIDTH, &width)){ if(!sizeProcessed){ maxWidth = width; minWidth = width; sizeProcessed = true; thumbDir = TIFFCurrentDirectory(t); } else{ if(width > maxWidth) maxWidth = width; else if(width < minWidth){ minWidth = width; thumbDir = TIFFCurrentDirectory(t); } } } isValidDir = TIFFReadDirectory(t); } if(thumbDir != ((tdir_t)-1)){ if(minWidth != maxWidth) qWarning("Found small TIFF subimage, orig width: %d, small: %d", (unsigned int)maxWidth, (unsigned int)minWidth); if(TIFFSetDirectory(t, thumbDir)){ TIFFGetField(t, TIFFTAG_IMAGELENGTH, &height); img.create(minWidth, height, 32); if(!TIFFReadRGBAImage(t, minWidth, height, (uint32*) img.bits(), 0)) img.reset(); else{ uint32 r, b; unsigned int i, x, count, pixel, mid, *data, *midData; count = img.width()*img.height(); data = (unsigned int *)img.bits(); for(i=0; i < count; ++i){ pixel = data[i]; r = (0x00FF0000 & data[i]) >> 16; b = (0x000000FF & data[i]) << 16; pixel &= 0xFF00FF00; pixel += r+b; data[i] = pixel; } mid = height >> 1; for(i=0; i < mid; ++i){ data = (unsigned int *)img.scanLine(i); midData = (unsigned int *)img.scanLine(height-(i+1)); for(x=0; x < minWidth; ++x){ pixel = data[x]; data[x] = midData[x]; midData[x] = pixel; } } if(minWidth != maxWidth) qWarning("Returning TIFF subimage, size %d,%d", img.width(), img.height()); } } } TIFFClose(t); return(!img.isNull()); } bool appendTooltipData(const char *filename, QString &imageStr, QString &cameraStr, QString &commentStr, bool isRTF) { const char *ext = extension(filename); if(qstricmp(ext, "png") == 0) return(appendPNGTooltipData(filename, imageStr, commentStr, isRTF)); else if(qstricmp(ext, "gif") == 0) return(appendGIFTooltipData(filename, imageStr, isRTF)); else if(qstricmp(ext, "jpg") == 0 || qstricmp(ext, "jpeg") == 0) return(appendJPEGTooltipData(filename, imageStr, cameraStr, commentStr, isRTF)); else if(qstricmp(ext, "tif") == 0 || qstricmp(ext, "tiff") == 0) return(appendTIFFTooltipData(filename, imageStr, cameraStr, commentStr, isRTF)); else if(qstricmp(ext, "bmp") == 0) return(appendBMPTooltipData(filename, imageStr, isRTF)); return(false); } bool appendGIFTooltipData(const char *filename, QString &imageStr, bool isRTF) { static char buffer[1024]; int i; uint16 w, h; QString nl(isRTF ? "
" : "\n"); // Using QFile and streams is overkill, but I like the free conversions // for the size >:) We just use open() and read() for checking animation. QFile f(filename); if(!f.open(IO_ReadOnly)) return(false); QDataStream stream(&f); stream.setByteOrder(QDataStream::LittleEndian); stream.readRawBytes(buffer, 3); if(qstrncmp(buffer, "GIF", 3) != 0){ f.close(); return(false); } stream.readRawBytes(buffer, 3); buffer[3] = '\0'; stream >> w; stream >> h; imageStr += i18n("Image size: ") + QString().sprintf("%dx%d", w, h) + nl; imageStr += i18n("Version: ") + buffer + nl; stream.readRawBytes(buffer, 1024); f.close(); imageStr += i18n("Animated: "); for(i=0; i < 1024; ++i){ if(buffer[i] == 'N'){ if(qstrncmp(buffer+i, "NETSCAPE", 8) == 0){ imageStr += i18n("yes") + nl; return(true); } } else if(buffer[i] == 'A'){ if(qstrncmp(buffer+i, "ANIMEXTS", 8) == 0){ imageStr += i18n("yes") + nl; return(true); } } } imageStr += i18n("no") + nl; return(true); } bool appendBMPTooltipData(const char *filename, QString &imageStr, bool isRTF) { static char buffer[2]; QString typeStr, compStr; QString nl(isRTF ? "
" : "\n"); QFile f(filename); if(!f.open(IO_ReadOnly)) return(false); QDataStream stream(&f); stream.setByteOrder(QDataStream::LittleEndian); stream.readRawBytes(buffer, 2); if(qstrncmp(buffer, "BM", 2) == 0) typeStr = i18n("Windows BMP"); else if(qstrncmp(buffer, "BA", 2) == 0) typeStr = i18n("OS/2 Bitmap"); else if(qstrncmp(buffer, "CI", 2) == 0 || qstrncmp(buffer, "IC", 2) == 0) typeStr = i18n("OS/2 Icon"); else if(qstrncmp(buffer, "CP", 2) == 0 || qstrncmp(buffer, "PT", 2) == 0) typeStr = i18n("OS/2 Pointer"); else{ f.close(); return(false); } uint32 temp, w, h, compression; uint16 planes, bits; stream >> temp; // skip this stuff stream >> temp; stream >> temp; stream >> temp; stream >> w; stream >> h; stream >> planes; stream >> bits; stream >> compression; if(compression == 0) compStr = i18n("None"); else if(compression == 1) compStr = i18n("RLE 8bpp"); else if(compression == 2) compStr = i18n("RLE 4bpp"); else if(compression == 3) compStr = i18n("Bitfields"); else compStr = i18n("Unknown"); imageStr += i18n("Image size: ") + QString().sprintf("%ux%u", (unsigned int)w, (unsigned int)h) + nl; imageStr += i18n("Type: ") + typeStr + ", "; imageStr += i18n("Compression: ") + compStr + nl; f.close(); return(true); } bool appendPNGTooltipData(const char *filename, QString &imageStr, QString &commentStr, bool isRTF) { int num_text=0; png_textp text_ptr; png_structp png_ptr; png_infop info_ptr; //unsigned int sig_read = 0; png_uint_32 width, height; int bit_depth, color_type, interlace_type; FILE *fp; QString nl(isRTF ? "
" : "\n"); if((fp = fopen(filename, "rb")) == NULL) return(false); png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if(!png_ptr){ fclose(fp); return(false); } info_ptr = png_create_info_struct(png_ptr); if(!info_ptr){ fclose(fp); png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); return(-1); } if(setjmp(png_jmpbuf(png_ptr))){ png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); fclose(fp); return(-1); } png_init_io(png_ptr, fp); png_read_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL); imageStr += i18n("Image size: ") + QString().sprintf("%dx%d",(unsigned int)width, (unsigned int)height) + nl; QString tmpStr; switch(color_type){ case PNG_COLOR_TYPE_GRAY: tmpStr = i18n("Grayscale"); break; case PNG_COLOR_TYPE_PALETTE: tmpStr = i18n("Palette"); break; case PNG_COLOR_TYPE_RGB: tmpStr = i18n("RGB"); break; case PNG_COLOR_TYPE_RGB_ALPHA: tmpStr = i18n("RGB w/alpha"); break; case PNG_COLOR_TYPE_GRAY_ALPHA: tmpStr = i18n("Grayscale w/alpha"); break; default: tmpStr = i18n("Unknown!"); break; } imageStr += i18n("Color type: ") + tmpStr + ", " + i18n("Bit Depth: ") + QString().setNum(bit_depth) + nl; png_get_text(png_ptr,info_ptr, &text_ptr, &num_text); while(num_text--){ commentStr += QString(text_ptr->text) + nl; text_ptr++; } // finished png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); fclose(fp); return(true); } bool appendJPEGTooltipData(const char *filename, QString &imageStr, QString &cameraStr, QString &commentStr, bool isRTF) { MyExifData ImageInfo; bool needsNewline; QString tmpStr; QString nl(isRTF ? "
" : "\n"); if(!ImageInfo.scan(filename)){ qWarning("Could not scan JPEG file %s!", filename); return(false); } // // camera info // needsNewline = false; tmpStr = ImageInfo.getCameraModel(); if(!tmpStr.isEmpty()){ cameraStr += i18n("Camera Model: ") + tmpStr; needsNewline = true; } tmpStr = ImageInfo.getCameraMake(); if(!tmpStr.isEmpty()){ needsNewline = true; if(cameraStr.isEmpty()) cameraStr += i18n("Camera Make: ") + tmpStr; else cameraStr += ", " + i18n("Make: ") + tmpStr; } if(needsNewline) cameraStr += nl; tmpStr = ImageInfo.getDateTime(); if(!tmpStr.isEmpty()) cameraStr += i18n("Photo taken on: ") + tmpStr + nl; if(ImageInfo.getFlashUsed()) cameraStr += i18n("Flash used") + nl; if(ImageInfo.getFocalLength()){ cameraStr += QString().sprintf("Focal length: %4.1f", ImageInfo.getFocalLength()); if(ImageInfo.getCCDWidth()){ cameraStr += ", " + i18n("35mm equivalent: ") + QString().setNum(ImageInfo.getFocalLength()/ ImageInfo.getCCDWidth()*35 + 0.5); } cameraStr += nl; } if(ImageInfo.getCCDWidth()){ cameraStr += i18n("CCD width :") + QString().sprintf("%4.2f", ImageInfo.getCCDWidth()) + nl; } float exposureTime = ImageInfo.getExposureTime(); if(exposureTime){ if(exposureTime > 0 && exposureTime <= 0.5){ cameraStr += i18n("Exposure time: ") + QString().sprintf("(1/%d)", (int)(0.5 + 1/exposureTime)) + nl; } } if(ImageInfo.getApertureFNumber()){ cameraStr += i18n("Aperture: ") + QString().sprintf("f/%3.1f", (double)ImageInfo.getApertureFNumber()) + nl; } if(ImageInfo.getDistance()){ cameraStr += i18n("Focus distance: "); if(ImageInfo.getDistance() < 0) cameraStr += i18n("Infinite"); else cameraStr += QString().sprintf("%5.2fm", (double)ImageInfo.getDistance()); cameraStr += nl; } if(ImageInfo.getExposureBias()){ cameraStr += i18n("Exposure bias: ") + QString().sprintf("%4.2f", (double)ImageInfo.getExposureBias()) + nl; } if(ImageInfo.getWhitebalance() != -1){ cameraStr += i18n("White balance: "); switch(ImageInfo.getWhitebalance()){ case 0: cameraStr += i18n("Unknown"); break; case 1: cameraStr += i18n("Daylight"); break; case 2: cameraStr += i18n("Fluorescent"); break; case 3: //tag=i18n("incandescent"); cameraStr += i18n("Tungsten"); break; case 17: cameraStr += i18n("Standard light A"); break; case 18: cameraStr += i18n("Standard light B"); break; case 19: cameraStr += i18n("Standard light C"); break; case 20: cameraStr += i18n("D55"); break; case 21: cameraStr += i18n("D65"); break; case 22: cameraStr += i18n("D75"); break; case 255: cameraStr += i18n("Other"); break; default: //23 to 254 = reserved cameraStr += i18n("Unknown"); } cameraStr += nl; } if(ImageInfo.getMeteringMode() != -1){ cameraStr += i18n("Metering mode: "); switch(ImageInfo.getMeteringMode()) { case 0: cameraStr += i18n("Unknown"); break; case 1: cameraStr += i18n("Average"); break; case 2: cameraStr += i18n("Center weighted average"); break; case 3: cameraStr += i18n("Spot"); break; case 4: cameraStr += i18n("MultiSpot"); break; case 5: cameraStr += i18n("Pattern"); break; case 6: cameraStr += i18n("Partial"); break; case 255: cameraStr += i18n("Other"); break; default: // 7 to 254 = reserved cameraStr += i18n("Unknown"); } cameraStr += nl; } if(ImageInfo.getExposureProgram()){ cameraStr += i18n("Exposure: "); switch(ImageInfo.getExposureProgram()) { case 0: cameraStr += i18n("Not defined"); break; case 1: cameraStr += i18n("Manual"); break; case 2: cameraStr += i18n("Normal program"); break; case 3: cameraStr += i18n("Aperture priority"); break; case 4: cameraStr += i18n("Shutter priority"); break; case 5: cameraStr += i18n("Creative program (biased to fast shutter)"); break; case 6: cameraStr += i18n("Action program (biased to fast shutter)"); break; case 7: cameraStr += i18n("Portrait mode (closeup photos)"); break; case 8: cameraStr += i18n("Landscape mode (landscape photos)"); break; default: // 9 to 255 = reserved cameraStr += i18n("Unknown"); } cameraStr += nl; } if(ImageInfo.getISOequivalent()){ cameraStr += i18n("ISO equivalent: ") + QString().sprintf("%2d", (int)ImageInfo.getISOequivalent()) + nl; } // // image info // imageStr += i18n("Image size: ") + QString().sprintf("%dx%d", ImageInfo.getWidth(), ImageInfo.getHeight()) + nl; if(ImageInfo.getOrientation()){ // FIXME: format values imageStr += i18n("Orientation: ") + QString().setNum(ImageInfo.getOrientation()) + nl; } imageStr += i18n("Color mode: ") + (ImageInfo.getIsColor() ? i18n("Color") : i18n("Black and white")) + nl; if(ImageInfo.getCompressionLevel()){ imageStr += i18n("Quality: "); switch(ImageInfo.getCompressionLevel()){ case 1: imageStr += i18n("Basic"); break; case 2: imageStr += i18n("Normal"); break; case 4: imageStr += i18n("Fine"); break; default: imageStr += i18n("Unknown"); } imageStr += nl; } // // comment info // tmpStr = ImageInfo.getComment(); QString userStr = ImageInfo.getUserComment(); if(!tmpStr.isEmpty() && !userStr.isEmpty()){ commentStr += i18n("Image comment: ") + nl + tmpStr + nl; commentStr += i18n("User comment: ") + nl + userStr + nl; } else if(!userStr.isEmpty()) commentStr += userStr + nl; else if(!tmpStr.isEmpty()) commentStr += tmpStr + nl; return(true); } bool appendTIFFTooltipData(const char *filename, QString &imageStr, QString &scannerStr, QString &commentStr, bool isRTF) { TIFF *t = TIFFOpen(filename, "r"); if(!t) return(false); uint32 param32; uint16 pages=0, bps=0, spp=0, alpha=0, resunit=0, color=0, compression=0, tmp=0; float xres=0.0, yres=0.0; char *str; QString nl(isRTF ? "
" : "\n"); // // image info // TIFFGetField(t, TIFFTAG_IMAGEWIDTH, ¶m32); imageStr += i18n("Image size: ") + QString().setNum(param32) + "x"; TIFFGetField(t, TIFFTAG_IMAGELENGTH, ¶m32); imageStr += QString().setNum(param32) + nl; TIFFGetField(t, TIFFTAG_XRESOLUTION, &xres); TIFFGetField(t, TIFFTAG_YRESOLUTION, &yres); TIFFGetField(t, TIFFTAG_MATTEING, &alpha); TIFFGetField(t, TIFFTAG_PHOTOMETRIC, &color); TIFFGetField(t, TIFFTAG_PAGENUMBER, &tmp, &pages); TIFFGetFieldDefaulted(t, TIFFTAG_BITSPERSAMPLE, &bps); TIFFGetFieldDefaulted(t, TIFFTAG_SAMPLESPERPIXEL, &spp); TIFFGetFieldDefaulted(t, TIFFTAG_RESOLUTIONUNIT, &resunit); TIFFGetFieldDefaulted(t, TIFFTAG_COMPRESSION, &compression); imageStr += i18n("Color mode: "); switch(color){ case PHOTOMETRIC_MINISWHITE: case PHOTOMETRIC_MINISBLACK: imageStr += i18n("Black and white"); break; case PHOTOMETRIC_RGB:{ if(alpha) imageStr += i18n("RGBA"); else imageStr += i18n("RGB"); break; } case PHOTOMETRIC_PALETTE: imageStr += i18n("Palette"); break; case PHOTOMETRIC_MASK: imageStr += i18n("Alpha mask"); break; case PHOTOMETRIC_SEPARATED: imageStr += i18n("Separated color"); break; case PHOTOMETRIC_YCBCR: imageStr += i18n("YCbCr CCIR 60"); break; case PHOTOMETRIC_CIELAB: imageStr += i18n("CIE Lab"); break; case PHOTOMETRIC_LOGL: imageStr += i18n("CIE LogL"); break; case PHOTOMETRIC_LOGLUV: imageStr += i18n("CIE LogLuv"); break; default: imageStr += i18n("Unknown"); break; } imageStr += ", " + i18n("Bits Per Pixel: ") + QString().setNum(bps*spp) + nl; if(resunit != RESUNIT_NONE) { if(resunit == RESUNIT_CENTIMETER){ xres *= 2.54; yres *= 2.54; } imageStr += i18n("Resolution : ") + QString().setNum((int)xres) + "x" + QString().setNum((int)yres) + nl; } imageStr += i18n("Compression: "); switch(compression){ case COMPRESSION_NONE: imageStr += i18n("None"); break; case COMPRESSION_CCITTRLE: imageStr += i18n("CCITT modified Huffman RLE"); break; case COMPRESSION_CCITTFAX3: imageStr += i18n("CCITT G3 fax"); break; case COMPRESSION_CCITTFAX4: imageStr += i18n("CCITT G4 fax"); break; case COMPRESSION_LZW: imageStr += i18n("LZW"); break; case COMPRESSION_OJPEG: imageStr += i18n("JPEG"); break; case COMPRESSION_JPEG: imageStr += i18n("JPEG DCT"); break; case COMPRESSION_NEXT: imageStr += i18n("NeXT 2-bit RLE"); break; case COMPRESSION_CCITTRLEW: imageStr += i18n("#1 RLE word alignment"); break; case COMPRESSION_PACKBITS: imageStr += i18n("Macintosh RLE"); break; case COMPRESSION_THUNDERSCAN: imageStr += i18n("Thunderscan RLE"); break; case COMPRESSION_IT8CTPAD: imageStr += i18n("IT8 padded CT"); break; case COMPRESSION_IT8LW: imageStr += i18n("IT8 linework RLE"); break; case COMPRESSION_IT8MP: imageStr += i18n("IT8 monochrome picture"); break; case COMPRESSION_IT8BL: imageStr += i18n("IT8 binary lineart"); break; case COMPRESSION_PIXARFILM: imageStr += i18n("Pixar 10bit LZW"); break; case COMPRESSION_PIXARLOG: imageStr += i18n("Pixar 11bit Zip"); break; case COMPRESSION_DEFLATE: imageStr += i18n("Deflate"); break; case COMPRESSION_ADOBE_DEFLATE: imageStr += i18n("Adobe Deflate"); break; case COMPRESSION_DCS: imageStr += i18n("Kodak DCS"); break; case COMPRESSION_JBIG: imageStr += i18n("ISO JBIG"); break; case COMPRESSION_SGILOG: imageStr += i18n("SGI Log Luminance RLE"); break; case COMPRESSION_SGILOG24: imageStr += i18n("SGI Log 24bit packed"); break; default: imageStr += i18n("Unknown"); break; } imageStr += nl; if(pages && (compression==COMPRESSION_CCITTFAX3 || compression==COMPRESSION_CCITTFAX4)) imageStr += i18n("Pages: ") + QString().number(pages) + nl; // // scanner info // str = NULL; TIFFGetField(t, TIFFTAG_MAKE, &str); if(str) scannerStr += i18n("Scanner make: ") + str + nl; str = NULL; TIFFGetField(t, TIFFTAG_MODEL, &str); if(str) scannerStr += i18n("Scanner model: ") + str + nl; str = NULL; TIFFGetField(t, TIFFTAG_SOFTWARE, &str); if(str) scannerStr += i18n("Software: ") + str + nl; str = NULL; TIFFGetField(t, TIFFTAG_DATETIME, &str); if(str) scannerStr += i18n("Image taken on: ") + str + nl; // // comment info // str = NULL; TIFFGetField(t, TIFFTAG_ARTIST, &str); if(str) commentStr += i18n("Artist: ") + str + nl; str = NULL; TIFFGetField(t, TIFFTAG_COPYRIGHT, &str); if(str) commentStr += i18n("Copyright: ") + str + nl; str = NULL; TIFFGetField(t, TIFFTAG_IMAGEDESCRIPTION, &str); if(str) commentStr += i18n("Description: ") + str + nl; TIFFClose(t); return(true); }