/// <summary>
/// Sets the value(s) of a tag in a TIFF file/stream open for writing.
/// </summary>
/// <param name="tif">An instance of the <see cref="Tiff"/> class.</param>
/// <param name="tag">The tag.</param>
/// <param name="value">The tag value(s).</param>
/// <returns>
/// <c>true</c> if tag value(s) were set successfully; otherwise, <c>false</c>.
/// </returns>
/// <seealso cref="Tiff.SetField"/>
public virtual bool SetField(Tiff tif, TiffTag tag, FieldValue[] value)
{
const string module = "vsetfield";
TiffDirectory td = tif.m_dir;
bool status = true;
int v32 = 0;
int v = 0;
bool end = false;
bool badvalue = false;
bool badvalue32 = false;
switch (tag)
{
case TiffTag.SUBFILETYPE:
td.td_subfiletype = (FileType)value[0].ToByte();
break;
case TiffTag.IMAGEWIDTH:
td.td_imagewidth = value[0].ToInt();
break;
case TiffTag.IMAGELENGTH:
td.td_imagelength = value[0].ToInt();
break;
case TiffTag.BITSPERSAMPLE:
td.td_bitspersample = value[0].ToShort();
// If the data require post-decoding processing to byte-swap samples, set it
// up here. Note that since tags are required to be ordered, compression code
// can override this behavior in the setup method if it wants to roll the post
// decoding work in with its normal work.
if ((tif.m_flags & TiffFlags.SWAB) == TiffFlags.SWAB)
{
if (td.td_bitspersample == 16)
{
tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmSwab16Bit;
}
else if (td.td_bitspersample == 24)
{
tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmSwab24Bit;
}
else if (td.td_bitspersample == 32)
{
tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmSwab32Bit;
}
else if (td.td_bitspersample == 64)
{
tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmSwab64Bit;
}
else if (td.td_bitspersample == 128)
{
// two 64's
tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmSwab64Bit;
}
}
break;
case TiffTag.COMPRESSION:
v = value[0].ToInt() & 0xffff;
Compression comp = (Compression)v;
// If we're changing the compression scheme, then notify the previous module
// so that it can cleanup any state it's setup.
if (tif.fieldSet(FieldBit.Compression))
{
if (td.td_compression == comp)
{
break;
}
tif.m_currentCodec.Cleanup();
tif.m_flags &= ~TiffFlags.CODERSETUP;
}
// Setup new compression scheme.
status = tif.setCompressionScheme(comp);
if (status)
{
td.td_compression = comp;
}
else
{
status = false;
}
break;
case TiffTag.PHOTOMETRIC:
td.td_photometric = (Photometric)value[0].ToInt();
break;
case TiffTag.THRESHHOLDING:
td.td_threshholding = (Threshold)value[0].ToByte();
break;
case TiffTag.FILLORDER:
v = value[0].ToInt();
FillOrder fo = (FillOrder)v;
if (fo != FillOrder.LSB2MSB && fo != FillOrder.MSB2LSB)
{
badvalue = true;
break;
}
td.td_fillorder = fo;
break;
case TiffTag.ORIENTATION:
v = value[0].ToInt();
Orientation or = (Orientation)v;
if (or < Orientation.TOPLEFT || Orientation.LEFTBOT < or)
{
badvalue = true;
break;
}
else
{
td.td_orientation = or;
}
break;
case TiffTag.SAMPLESPERPIXEL:
// XXX should cross check - e.g. if pallette, then 1
v = value[0].ToInt();
if (v == 0)
{
badvalue = true;
break;
}
td.td_samplesperpixel = (short)v;
break;
case TiffTag.ROWSPERSTRIP:
v32 = value[0].ToInt();
if (v32 == 0)
{
badvalue32 = true;
break;
}
td.td_rowsperstrip = v32;
if (!tif.fieldSet(FieldBit.TileDimensions))
{
td.td_tilelength = v32;
td.td_tilewidth = td.td_imagewidth;
}
break;
case TiffTag.MINSAMPLEVALUE:
td.td_minsamplevalue = value[0].ToUShort();
break;
case TiffTag.MAXSAMPLEVALUE:
td.td_maxsamplevalue = value[0].ToUShort();
break;
case TiffTag.SMINSAMPLEVALUE:
td.td_sminsamplevalue = value[0].ToDouble();
break;
case TiffTag.SMAXSAMPLEVALUE:
td.td_smaxsamplevalue = value[0].ToDouble();
break;
case TiffTag.XRESOLUTION:
td.td_xresolution = value[0].ToFloat();
break;
case TiffTag.YRESOLUTION:
td.td_yresolution = value[0].ToFloat();
break;
case TiffTag.PLANARCONFIG:
v = value[0].ToInt();
PlanarConfig pc = (PlanarConfig)v;
if (pc != PlanarConfig.CONTIG && pc != PlanarConfig.SEPARATE)
{
badvalue = true;
break;
}
td.td_planarconfig = pc;
break;
case TiffTag.XPOSITION:
td.td_xposition = value[0].ToFloat();
break;
case TiffTag.YPOSITION:
td.td_yposition = value[0].ToFloat();
break;
case TiffTag.RESOLUTIONUNIT:
v = value[0].ToInt();
ResUnit ru = (ResUnit)v;
if (ru < ResUnit.NONE || ResUnit.CENTIMETER < ru)
{
badvalue = true;
break;
}
td.td_resolutionunit = ru;
break;
case TiffTag.PAGENUMBER:
td.td_pagenumber[0] = value[0].ToShort();
td.td_pagenumber[1] = value[1].ToShort();
break;
case TiffTag.HALFTONEHINTS:
td.td_halftonehints[0] = value[0].ToShort();
td.td_halftonehints[1] = value[1].ToShort();
break;
case TiffTag.COLORMAP:
v32 = 1 << td.td_bitspersample;
Tiff.setShortArray(out td.td_colormap[0], value[0].ToShortArray(), v32);
Tiff.setShortArray(out td.td_colormap[1], value[1].ToShortArray(), v32);
Tiff.setShortArray(out td.td_colormap[2], value[2].ToShortArray(), v32);
break;
case TiffTag.EXTRASAMPLES:
if (!setExtraSamples(td, ref v, value))
{
badvalue = true;
break;
}
break;
case TiffTag.MATTEING:
if (value[0].ToShort() != 0)
{
td.td_extrasamples = 1;
}
else
{
td.td_extrasamples = 0;
}
if (td.td_extrasamples != 0)
{
td.td_sampleinfo = new ExtraSample[1];
td.td_sampleinfo[0] = ExtraSample.ASSOCALPHA;
}
break;
case TiffTag.TILEWIDTH:
v32 = value[0].ToInt();
if ((v32 % 16) != 0)
{
if (tif.m_mode != Tiff.O_RDONLY)
{
badvalue32 = true;
break;
}
Tiff.WarningExt(tif, tif.m_clientdata, tif.m_name,
"Nonstandard tile width {0}, convert file", v32);
}
td.td_tilewidth = v32;
tif.m_flags |= TiffFlags.ISTILED;
break;
case TiffTag.TILELENGTH:
v32 = value[0].ToInt();
if ((v32 % 16) != 0)
{
if (tif.m_mode != Tiff.O_RDONLY)
{
badvalue32 = true;
break;
}
Tiff.WarningExt(tif, tif.m_clientdata, tif.m_name,
"Nonstandard tile length {0}, convert file", v32);
}
td.td_tilelength = v32;
tif.m_flags |= TiffFlags.ISTILED;
break;
case TiffTag.TILEDEPTH:
v32 = value[0].ToInt();
if (v32 == 0)
{
badvalue32 = true;
break;
}
td.td_tiledepth = v32;
break;
case TiffTag.DATATYPE:
v = value[0].ToInt();
SampleFormat sf = SampleFormat.VOID;
switch (v)
{
case DATATYPE_VOID:
sf = SampleFormat.VOID;
break;
case DATATYPE_INT:
sf = SampleFormat.INT;
break;
case DATATYPE_UINT:
sf = SampleFormat.UINT;
break;
case DATATYPE_IEEEFP:
sf = SampleFormat.IEEEFP;
break;
default:
badvalue = true;
break;
}
if (!badvalue)
{
td.td_sampleformat = sf;
}
break;
case TiffTag.SAMPLEFORMAT:
v = value[0].ToInt();
sf = (SampleFormat)v;
if (sf < SampleFormat.UINT || SampleFormat.COMPLEXIEEEFP < sf)
{
badvalue = true;
break;
}
td.td_sampleformat = sf;
// Try to fix up the SWAB function for complex data.
if (td.td_sampleformat == SampleFormat.COMPLEXINT &&
td.td_bitspersample == 32 && tif.m_postDecodeMethod == Tiff.PostDecodeMethodType.pdmSwab32Bit)
{
tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmSwab16Bit;
}
else if ((td.td_sampleformat == SampleFormat.COMPLEXINT ||
td.td_sampleformat == SampleFormat.COMPLEXIEEEFP) &&
td.td_bitspersample == 64 && tif.m_postDecodeMethod == Tiff.PostDecodeMethodType.pdmSwab64Bit)
{
tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmSwab32Bit;
}
break;
case TiffTag.IMAGEDEPTH:
td.td_imagedepth = value[0].ToInt();
break;
case TiffTag.SUBIFD:
if ((tif.m_flags & TiffFlags.INSUBIFD) != TiffFlags.INSUBIFD)
{
td.td_nsubifd = value[0].ToShort();
Tiff.setLongArray(out td.td_subifd, value[1].ToIntArray(), td.td_nsubifd);
}
else
{
Tiff.ErrorExt(tif, tif.m_clientdata, module,
"{0}: Sorry, cannot nest SubIFDs", tif.m_name);
status = false;
}
break;
case TiffTag.YCBCRPOSITIONING:
td.td_ycbcrpositioning = (YCbCrPosition)value[0].ToByte();
break;
case TiffTag.YCBCRSUBSAMPLING:
td.td_ycbcrsubsampling[0] = value[0].ToShort();
td.td_ycbcrsubsampling[1] = value[1].ToShort();
break;
case TiffTag.TRANSFERFUNCTION:
v = ((td.td_samplesperpixel - td.td_extrasamples) > 1 ? 3 : 1);
for (int i = 0; i < v; i++)
{
Tiff.setShortArray(out td.td_transferfunction[i], value[0].ToShortArray(), 1 << td.td_bitspersample);
}
break;
case TiffTag.REFERENCEBLACKWHITE:
// XXX should check for null range
Tiff.setFloatArray(out td.td_refblackwhite, value[0].ToFloatArray(), 6);
break;
case TiffTag.INKNAMES:
v = value[0].ToInt();
string s = value[1].ToString();
v = checkInkNamesString(tif, v, s);
status = v > 0;
if (v > 0)
{
setNString(out td.td_inknames, s, v);
td.td_inknameslen = v;
}
break;
default:
// This can happen if multiple images are open with
// different codecs which have private tags. The global tag
// information table may then have tags that are valid for
// one file but not the other. If the client tries to set a
// tag that is not valid for the image's codec then we'll
// arrive here. This happens, for example, when tiffcp is
// used to convert between compression schemes and
// codec-specific tags are blindly copied.
TiffFieldInfo fip = tif.FindFieldInfo(tag, TiffType.ANY);
if (fip == null || fip.Bit != FieldBit.Custom)
{
Tiff.ErrorExt(tif, tif.m_clientdata, module,
"{0}: Invalid {1}tag \"{2}\" (not supported by codec)",
tif.m_name, Tiff.isPseudoTag(tag) ? "pseudo-" : string.Empty,
fip != null ? fip.Name : "Unknown");
status = false;
break;
}
// Find the existing entry for this custom value.
int tvIndex = -1;
for (int iCustom = 0; iCustom < td.td_customValueCount; iCustom++)
{
if (td.td_customValues[iCustom].info.Tag == tag)
{
tvIndex = iCustom;
td.td_customValues[iCustom].value = null;
break;
}
}
// Grow the custom list if the entry was not found.
if (tvIndex == -1)
{
td.td_customValueCount++;
TiffTagValue[] new_customValues = Tiff.Realloc(
td.td_customValues, td.td_customValueCount - 1, td.td_customValueCount);
td.td_customValues = new_customValues;
tvIndex = td.td_customValueCount - 1;
td.td_customValues[tvIndex].info = fip;
td.td_customValues[tvIndex].value = null;
td.td_customValues[tvIndex].count = 0;
}
// Set custom value ... save a copy of the custom tag value.
int tv_size = Tiff.dataSize(fip.Type);
if (tv_size == 0)
{
status = false;
Tiff.ErrorExt(tif, tif.m_clientdata, module,
"{0}: Bad field type {1} for \"{2}\"",
tif.m_name, fip.Type, fip.Name);
end = true;
break;
}
int paramIndex = 0;
if (fip.PassCount)
{
if (fip.WriteCount == TiffFieldInfo.Variable2)
{
td.td_customValues[tvIndex].count = value[paramIndex++].ToInt();
}
else
{
td.td_customValues[tvIndex].count = value[paramIndex++].ToInt();
}
}
else if (fip.WriteCount == TiffFieldInfo.Variable ||
fip.WriteCount == TiffFieldInfo.Variable2)
{
td.td_customValues[tvIndex].count = 1;
}
else if (fip.WriteCount == TiffFieldInfo.Spp)
{
td.td_customValues[tvIndex].count = td.td_samplesperpixel;
}
else
{
td.td_customValues[tvIndex].count = fip.WriteCount;
}
if (fip.Type == TiffType.ASCII)
{
string ascii;
Tiff.setString(out ascii, value[paramIndex++].ToString());
td.td_customValues[tvIndex].value = Tiff.Latin1Encoding.GetBytes(ascii);
}
else
{
td.td_customValues[tvIndex].value = new byte[tv_size * td.td_customValues[tvIndex].count];
if ((fip.PassCount ||
fip.WriteCount == TiffFieldInfo.Variable ||
fip.WriteCount == TiffFieldInfo.Variable2 ||
fip.WriteCount == TiffFieldInfo.Spp ||
td.td_customValues[tvIndex].count > 1) &&
fip.Tag != TiffTag.PAGENUMBER &&
fip.Tag != TiffTag.HALFTONEHINTS &&
fip.Tag != TiffTag.YCBCRSUBSAMPLING &&
fip.Tag != TiffTag.DOTRANGE)
{
byte[] apBytes = value[paramIndex++].GetBytes();
Buffer.BlockCopy(apBytes, 0, td.td_customValues[tvIndex].value, 0, Math.Min(apBytes.Length, td.td_customValues[tvIndex].value.Length));
}
else
{
// XXX: The following loop required to handle
// PAGENUMBER, HALFTONEHINTS,
// YCBCRSUBSAMPLING and DOTRANGE tags.
// These tags are actually arrays and should be
// passed as arrays to SetField() function, but
// actually passed as a list of separate values.
// This behavior must be changed in the future!
// Upd: This loop also processes some EXIF tags with
// UNDEFINED type (like EXIF_FILESOURCE or EXIF_SCENETYPE)
// In this case input value is string-based, so
// in TiffType.UNDEFINED case we use FieldValue.GetBytes()[0]
// construction instead of direct call of FieldValue.ToByte() method.
byte[] val = td.td_customValues[tvIndex].value;
int valPos = 0;
for (int i = 0; i < td.td_customValues[tvIndex].count; i++, valPos += tv_size)
{
switch (fip.Type)
{
case TiffType.BYTE:
case TiffType.UNDEFINED:
val[valPos] = value[paramIndex + i].GetBytes()[0];
break;
case TiffType.SBYTE:
val[valPos] = value[paramIndex + i].ToByte();
break;
case TiffType.SHORT:
Buffer.BlockCopy(BitConverter.GetBytes(value[paramIndex + i].ToShort()), 0, val, valPos, tv_size);
break;
case TiffType.SSHORT:
Buffer.BlockCopy(BitConverter.GetBytes(value[paramIndex + i].ToShort()), 0, val, valPos, tv_size);
break;
case TiffType.LONG:
case TiffType.IFD:
Buffer.BlockCopy(BitConverter.GetBytes(value[paramIndex + i].ToInt()), 0, val, valPos, tv_size);
break;
case TiffType.SLONG:
Buffer.BlockCopy(BitConverter.GetBytes(value[paramIndex + i].ToInt()), 0, val, valPos, tv_size);
break;
case TiffType.RATIONAL:
case TiffType.SRATIONAL:
case TiffType.FLOAT:
Buffer.BlockCopy(BitConverter.GetBytes(value[paramIndex + i].ToFloat()), 0, val, valPos, tv_size);
break;
case TiffType.DOUBLE:
Buffer.BlockCopy(BitConverter.GetBytes(value[paramIndex + i].ToDouble()), 0, val, valPos, tv_size);
break;
default:
Array.Clear(val, valPos, tv_size);
status = false;
break;
}
}
}
}
break;
}
if (!end && !badvalue && !badvalue32)
{
if (status)
{
tif.setFieldBit(tif.FieldWithTag(tag).Bit);
tif.m_flags |= TiffFlags.DIRTYDIRECT;
}
}
if (badvalue)
{
Tiff.ErrorExt(tif, tif.m_clientdata, module,
"{0}: Bad value {1} for \"{2}\" tag",
tif.m_name, v, tif.FieldWithTag(tag).Name);
return(false);
}
if (badvalue32)
{
Tiff.ErrorExt(tif, tif.m_clientdata, module,
"{0}: Bad value {1} for \"{2}\" tag",
tif.m_name, v32, tif.FieldWithTag(tag).Name);
return(false);
}
return(status);
}