/// <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;
}