/// <summary> /// Creates new instance of the <see cref="TiffRgbaImage"/> class. /// </summary> /// <param name="tif"> /// The instance of the <see cref="BitMiracle.LibTiff.Classic"/> class used to retrieve /// image data. /// </param> /// <param name="stopOnError"> /// if set to <c>true</c> then an error will terminate the conversion; otherwise "get" /// methods will continue processing data until all the possible data in the image have /// been requested. /// </param> /// <param name="errorMsg">The error message (if any) gets placed here.</param> /// <returns> /// New instance of the <see cref="TiffRgbaImage"/> class if the image specified /// by <paramref name="tif"/> can be converted to RGBA format; otherwise, <c>null</c> is /// returned and <paramref name="errorMsg"/> contains the reason why it is being /// rejected. /// </returns> public static TiffRgbaImage Create(Tiff tif, bool stopOnError, out string errorMsg) { errorMsg = null; // Initialize to normal values TiffRgbaImage img = new TiffRgbaImage(); img.row_offset = 0; img.col_offset = 0; img.redcmap = null; img.greencmap = null; img.bluecmap = null; img.req_orientation = Orientation.BOTLEFT; // It is the default img.tif = tif; img.stoponerr = stopOnError; FieldValue[] result = tif.GetFieldDefaulted(TiffTag.BITSPERSAMPLE); img.bitspersample = result[0].ToShort(); switch (img.bitspersample) { case 1: case 2: case 4: case 8: case 16: break; default: errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle images with {0}-bit samples", img.bitspersample); return null; } img.alpha = 0; result = tif.GetFieldDefaulted(TiffTag.SAMPLESPERPIXEL); img.samplesperpixel = result[0].ToShort(); result = tif.GetFieldDefaulted(TiffTag.EXTRASAMPLES); short extrasamples = result[0].ToShort(); byte[] sampleinfo = result[1].ToByteArray(); if (extrasamples >= 1) { switch ((ExtraSample)sampleinfo[0]) { case ExtraSample.UNSPECIFIED: if (img.samplesperpixel > 3) { // Workaround for some images without correct info about alpha channel img.alpha = ExtraSample.ASSOCALPHA; } break; case ExtraSample.ASSOCALPHA: // data is pre-multiplied case ExtraSample.UNASSALPHA: // data is not pre-multiplied img.alpha = (ExtraSample)sampleinfo[0]; break; } } if (Tiff.DEFAULT_EXTRASAMPLE_AS_ALPHA) { result = tif.GetField(TiffTag.PHOTOMETRIC); if (result == null) img.photometric = Photometric.MINISWHITE; if (extrasamples == 0 && img.samplesperpixel == 4 && img.photometric == Photometric.RGB) { img.alpha = ExtraSample.ASSOCALPHA; extrasamples = 1; } } int colorchannels = img.samplesperpixel - extrasamples; result = tif.GetFieldDefaulted(TiffTag.COMPRESSION); Compression compress = (Compression)result[0].ToInt(); result = tif.GetFieldDefaulted(TiffTag.PLANARCONFIG); PlanarConfig planarconfig = (PlanarConfig)result[0].ToShort(); result = tif.GetField(TiffTag.PHOTOMETRIC); if (result == null) { switch (colorchannels) { case 1: if (img.isCCITTCompression()) img.photometric = Photometric.MINISWHITE; else img.photometric = Photometric.MINISBLACK; break; case 3: img.photometric = Photometric.RGB; break; default: errorMsg = string.Format(CultureInfo.InvariantCulture, "Missing needed {0} tag", photoTag); return null; } } else img.photometric = (Photometric)result[0].ToInt(); switch (img.photometric) { case Photometric.PALETTE: result = tif.GetField(TiffTag.COLORMAP); if (result == null) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Missing required \"Colormap\" tag"); return null; } short[] red_orig = result[0].ToShortArray(); short[] green_orig = result[1].ToShortArray(); short[] blue_orig = result[2].ToShortArray(); // copy the colormaps so we can modify them int n_color = (1 << img.bitspersample); img.redcmap = new ushort[n_color]; img.greencmap = new ushort[n_color]; img.bluecmap = new ushort[n_color]; Buffer.BlockCopy(red_orig, 0, img.redcmap, 0, n_color * sizeof(ushort)); Buffer.BlockCopy(green_orig, 0, img.greencmap, 0, n_color * sizeof(ushort)); Buffer.BlockCopy(blue_orig, 0, img.bluecmap, 0, n_color * sizeof(ushort)); if (planarconfig == PlanarConfig.CONTIG && img.samplesperpixel != 1 && img.bitspersample < 8) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle contiguous data with {0}={1}, and {2}={3} and Bits/Sample={4}", photoTag, img.photometric, "Samples/pixel", img.samplesperpixel, img.bitspersample); return null; } break; case Photometric.MINISWHITE: case Photometric.MINISBLACK: if (planarconfig == PlanarConfig.CONTIG && img.samplesperpixel != 1 && img.bitspersample < 8) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle contiguous data with {0}={1}, and {2}={3} and Bits/Sample={4}", photoTag, img.photometric, "Samples/pixel", img.samplesperpixel, img.bitspersample); return null; } break; case Photometric.YCBCR: // It would probably be nice to have a reality check here. if (planarconfig == PlanarConfig.CONTIG) { // can rely on LibJpeg.Net to convert to RGB // XXX should restore current state on exit switch (compress) { case Compression.JPEG: // TODO: when complete tests verify complete desubsampling and // YCbCr handling, remove use of JPEGCOLORMODE in favor of native // handling tif.SetField(TiffTag.JPEGCOLORMODE, JpegColorMode.RGB); img.photometric = Photometric.RGB; break; default: // do nothing break; } } // TODO: if at all meaningful and useful, make more complete support check // here, or better still, refactor to let supporting code decide whether there // is support and what meaningfull error to return break; case Photometric.RGB: if (colorchannels < 3) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle RGB image with {0}={1}", "Color channels", colorchannels); return null; } break; case Photometric.SEPARATED: result = tif.GetFieldDefaulted(TiffTag.INKSET); InkSet inkset = (InkSet)result[0].ToByte(); if (inkset != InkSet.CMYK) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle separated image with {0}={1}", "InkSet", inkset); return null; } if (img.samplesperpixel < 4) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle separated image with {0}={1}", "Samples/pixel", img.samplesperpixel); return null; } break; case Photometric.LOGL: if (compress != Compression.SGILOG) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, LogL data must have {0}={1}", "Compression", Compression.SGILOG); return null; } tif.SetField(TiffTag.SGILOGDATAFMT, 3); // 8-bit RGB monitor values. img.photometric = Photometric.MINISBLACK; // little white lie img.bitspersample = 8; break; case Photometric.LOGLUV: if (compress != Compression.SGILOG && compress != Compression.SGILOG24) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, LogLuv data must have {0}={1} or {2}", "Compression", Compression.SGILOG, Compression.SGILOG24); return null; } if (planarconfig != PlanarConfig.CONTIG) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle LogLuv images with {0}={1}", "Planarconfiguration", planarconfig); return null; } tif.SetField(TiffTag.SGILOGDATAFMT, 3); // 8-bit RGB monitor values. img.photometric = Photometric.RGB; // little white lie img.bitspersample = 8; break; case Photometric.CIELAB: break; default: errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle image with {0}={1}", photoTag, img.photometric); return null; } img.Map = null; img.BWmap = null; img.PALmap = null; img.ycbcr = null; img.cielab = null; result = tif.GetField(TiffTag.IMAGEWIDTH); img.width = result[0].ToInt(); result = tif.GetField(TiffTag.IMAGELENGTH); img.height = result[0].ToInt(); result = tif.GetFieldDefaulted(TiffTag.ORIENTATION); img.orientation = (Orientation)result[0].ToByte(); img.isContig = !(planarconfig == PlanarConfig.SEPARATE && colorchannels > 1); if (img.isContig) { if (!img.pickContigCase()) { errorMsg = "Sorry, can not handle image"; return null; } } else { if (!img.pickSeparateCase()) { errorMsg = "Sorry, can not handle image"; return null; } } return img; }