mirror of https://github.com/SixLabors/ImageSharp
12 changed files with 897 additions and 422 deletions
@ -0,0 +1,114 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Iptc; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// The decoder metadata helper methods.
|
|||
/// </summary>
|
|||
internal static class TiffDecoderMetadataHelpers |
|||
{ |
|||
public static ImageMetadata CreateMetadata(this IList<TiffFrameMetadata> frames, bool ignoreMetadata, ByteOrder byteOrder) |
|||
{ |
|||
var coreMetadata = new ImageMetadata(); |
|||
|
|||
TiffFrameMetadata rootFrameMetadata = frames.First(); |
|||
switch (rootFrameMetadata.ResolutionUnit) |
|||
{ |
|||
case TiffResolutionUnit.None: |
|||
coreMetadata.ResolutionUnits = PixelResolutionUnit.AspectRatio; |
|||
break; |
|||
case TiffResolutionUnit.Inch: |
|||
coreMetadata.ResolutionUnits = PixelResolutionUnit.PixelsPerInch; |
|||
break; |
|||
case TiffResolutionUnit.Centimeter: |
|||
coreMetadata.ResolutionUnits = PixelResolutionUnit.PixelsPerCentimeter; |
|||
break; |
|||
} |
|||
|
|||
if (rootFrameMetadata.HorizontalResolution != null) |
|||
{ |
|||
coreMetadata.HorizontalResolution = rootFrameMetadata.HorizontalResolution.Value; |
|||
} |
|||
|
|||
if (rootFrameMetadata.VerticalResolution != null) |
|||
{ |
|||
coreMetadata.VerticalResolution = rootFrameMetadata.VerticalResolution.Value; |
|||
} |
|||
|
|||
TiffMetadata tiffMetadata = coreMetadata.GetTiffMetadata(); |
|||
tiffMetadata.ByteOrder = byteOrder; |
|||
tiffMetadata.BitsPerPixel = GetBitsPerPixel(rootFrameMetadata); |
|||
tiffMetadata.Compression = rootFrameMetadata.Compression; |
|||
|
|||
if (!ignoreMetadata) |
|||
{ |
|||
foreach (TiffFrameMetadata frame in frames) |
|||
{ |
|||
if (tiffMetadata.XmpProfile == null) |
|||
{ |
|||
byte[] buf = frame.GetArray<byte>(ExifTag.XMP, true); |
|||
if (buf != null) |
|||
{ |
|||
tiffMetadata.XmpProfile = buf; |
|||
} |
|||
} |
|||
|
|||
if (coreMetadata.IptcProfile == null) |
|||
{ |
|||
byte[] buf = frame.GetArray<byte>(ExifTag.IPTC, true); |
|||
if (buf != null) |
|||
{ |
|||
coreMetadata.IptcProfile = new IptcProfile(buf); |
|||
} |
|||
} |
|||
|
|||
if (coreMetadata.IccProfile == null) |
|||
{ |
|||
byte[] buf = frame.GetArray<byte>(ExifTag.IccProfile, true); |
|||
if (buf != null) |
|||
{ |
|||
coreMetadata.IccProfile = new IccProfile(buf); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return coreMetadata; |
|||
} |
|||
|
|||
private static TiffBitsPerPixel GetBitsPerPixel(TiffFrameMetadata firstFrameMetaData) |
|||
{ |
|||
ushort[] bitsPerSample = firstFrameMetaData.BitsPerSample; |
|||
var bitsPerPixel = 0; |
|||
foreach (var bps in bitsPerSample) |
|||
{ |
|||
bitsPerPixel += bps; |
|||
} |
|||
|
|||
if (bitsPerPixel == 24) |
|||
{ |
|||
return TiffBitsPerPixel.Pixel24; |
|||
} |
|||
else if (bitsPerPixel == 8) |
|||
{ |
|||
return TiffBitsPerPixel.Pixel8; |
|||
} |
|||
else if (bitsPerPixel == 1) |
|||
{ |
|||
return TiffBitsPerPixel.Pixel1; |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,328 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff |
|||
{ |
|||
internal class TiffEncoderEntriesCollector |
|||
{ |
|||
public List<IExifValue> Entries { get; } = new List<IExifValue>(); |
|||
|
|||
public void ProcessGeneral<TPixel>(Image<TPixel> image, bool preserveMetadata) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
=> new GeneralProcessor(this).Process(image, preserveMetadata); |
|||
|
|||
public void ProcessImageFormat(TiffEncoderCore encoder) |
|||
=> new ImageFormatProcessor(this).Process(encoder); |
|||
|
|||
public void Add(IExifValue entry) |
|||
{ |
|||
IExifValue exist = this.Entries.Find(t => t.Tag == entry.Tag); |
|||
if (exist != null) |
|||
{ |
|||
this.Entries.Remove(exist); |
|||
} |
|||
|
|||
this.Entries.Add(entry); |
|||
} |
|||
|
|||
private void AddInternal(IExifValue entry) => this.Entries.Add(entry); |
|||
|
|||
private class GeneralProcessor |
|||
{ |
|||
private readonly TiffEncoderEntriesCollector collector; |
|||
|
|||
public GeneralProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector; |
|||
|
|||
public void Process<TPixel>(Image<TPixel> image, bool preserveMetadata) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
TiffFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetTiffMetadata(); |
|||
|
|||
var width = new ExifLong(ExifTagValue.ImageWidth) |
|||
{ |
|||
Value = (uint)image.Width |
|||
}; |
|||
|
|||
var height = new ExifLong(ExifTagValue.ImageLength) |
|||
{ |
|||
Value = (uint)image.Height |
|||
}; |
|||
|
|||
var software = new ExifString(ExifTagValue.Software) |
|||
{ |
|||
Value = "ImageSharp" |
|||
}; |
|||
|
|||
this.collector.AddInternal(width); |
|||
this.collector.AddInternal(height); |
|||
this.collector.AddInternal(software); |
|||
|
|||
this.ProcessResolution(image.Metadata, frameMetadata); |
|||
|
|||
if (preserveMetadata) |
|||
{ |
|||
this.ProcessMetadata(frameMetadata); |
|||
} |
|||
} |
|||
|
|||
private void ProcessResolution(ImageMetadata imageMetadata, TiffFrameMetadata frameMetadata) |
|||
{ |
|||
SynchResolution(imageMetadata, frameMetadata); |
|||
|
|||
var xResolution = new ExifRational(ExifTagValue.XResolution) |
|||
{ |
|||
Value = frameMetadata.GetSingle<Rational>(ExifTag.XResolution) |
|||
}; |
|||
|
|||
var yResolution = new ExifRational(ExifTagValue.YResolution) |
|||
{ |
|||
Value = frameMetadata.GetSingle<Rational>(ExifTag.YResolution) |
|||
}; |
|||
|
|||
var resolutionUnit = new ExifShort(ExifTagValue.ResolutionUnit) |
|||
{ |
|||
Value = frameMetadata.GetSingle<ushort>(ExifTag.ResolutionUnit) |
|||
}; |
|||
|
|||
this.collector.AddInternal(xResolution); |
|||
this.collector.AddInternal(yResolution); |
|||
this.collector.AddInternal(resolutionUnit); |
|||
} |
|||
|
|||
private void ProcessMetadata(TiffFrameMetadata frameMetadata) |
|||
{ |
|||
foreach (IExifValue entry in frameMetadata.FrameTags) |
|||
{ |
|||
// todo: skip subIfd
|
|||
if (entry.DataType == ExifDataType.Ifd) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
switch (ExifTags.GetPart(entry.Tag)) |
|||
{ |
|||
case ExifParts.ExifTags: |
|||
case ExifParts.GpsTags: |
|||
break; |
|||
|
|||
case ExifParts.IfdTags: |
|||
if (!IsMetadata(entry.Tag)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
if (!this.collector.Entries.Exists(t => t.Tag == entry.Tag)) |
|||
{ |
|||
this.collector.AddInternal(entry.DeepClone()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void SynchResolution(ImageMetadata imageMetadata, TiffFrameMetadata tiffFrameMetadata) |
|||
{ |
|||
double xres = imageMetadata.HorizontalResolution; |
|||
double yres = imageMetadata.VerticalResolution; |
|||
|
|||
switch (imageMetadata.ResolutionUnits) |
|||
{ |
|||
case PixelResolutionUnit.AspectRatio: |
|||
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.None; |
|||
break; |
|||
case PixelResolutionUnit.PixelsPerInch: |
|||
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.Inch; |
|||
break; |
|||
case PixelResolutionUnit.PixelsPerCentimeter: |
|||
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.Centimeter; |
|||
break; |
|||
case PixelResolutionUnit.PixelsPerMeter: |
|||
{ |
|||
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.Centimeter; |
|||
xres = UnitConverter.MeterToCm(xres); |
|||
yres = UnitConverter.MeterToCm(yres); |
|||
} |
|||
|
|||
break; |
|||
default: |
|||
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.None; |
|||
break; |
|||
} |
|||
|
|||
tiffFrameMetadata.HorizontalResolution = xres; |
|||
tiffFrameMetadata.VerticalResolution = yres; |
|||
} |
|||
|
|||
private static bool IsMetadata(ExifTag tag) |
|||
{ |
|||
switch ((ExifTagValue)(ushort)tag) |
|||
{ |
|||
case ExifTagValue.DocumentName: |
|||
case ExifTagValue.ImageDescription: |
|||
case ExifTagValue.Make: |
|||
case ExifTagValue.Model: |
|||
case ExifTagValue.Software: |
|||
case ExifTagValue.DateTime: |
|||
case ExifTagValue.Artist: |
|||
case ExifTagValue.HostComputer: |
|||
case ExifTagValue.TargetPrinter: |
|||
case ExifTagValue.XMP: |
|||
case ExifTagValue.Rating: |
|||
case ExifTagValue.RatingPercent: |
|||
case ExifTagValue.ImageID: |
|||
case ExifTagValue.Copyright: |
|||
case ExifTagValue.MDLabName: |
|||
case ExifTagValue.MDSampleInfo: |
|||
case ExifTagValue.MDPrepDate: |
|||
case ExifTagValue.MDPrepTime: |
|||
case ExifTagValue.MDFileUnits: |
|||
case ExifTagValue.SEMInfo: |
|||
case ExifTagValue.XPTitle: |
|||
case ExifTagValue.XPComment: |
|||
case ExifTagValue.XPAuthor: |
|||
case ExifTagValue.XPKeywords: |
|||
case ExifTagValue.XPSubject: |
|||
return true; |
|||
default: |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private class ImageFormatProcessor |
|||
{ |
|||
private readonly TiffEncoderEntriesCollector collector; |
|||
|
|||
public ImageFormatProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector; |
|||
|
|||
public void Process(TiffEncoderCore encoder) |
|||
{ |
|||
var samplesPerPixel = new ExifLong(ExifTagValue.SamplesPerPixel) |
|||
{ |
|||
Value = GetSamplesPerPixel(encoder) |
|||
}; |
|||
|
|||
ushort[] bitsPerSampleValue = GetBitsPerSampleValue(encoder); |
|||
var bitPerSample = new ExifShortArray(ExifTagValue.BitsPerSample) |
|||
{ |
|||
Value = bitsPerSampleValue |
|||
}; |
|||
|
|||
ushort compressionType = GetCompressionType(encoder); |
|||
var compression = new ExifShort(ExifTagValue.Compression) |
|||
{ |
|||
Value = compressionType |
|||
}; |
|||
|
|||
var photometricInterpretation = new ExifShort(ExifTagValue.PhotometricInterpretation) |
|||
{ |
|||
Value = (ushort)encoder.PhotometricInterpretation |
|||
}; |
|||
|
|||
this.collector.Add(samplesPerPixel); |
|||
this.collector.Add(bitPerSample); |
|||
this.collector.Add(compression); |
|||
this.collector.Add(photometricInterpretation); |
|||
|
|||
if (encoder.UseHorizontalPredictor) |
|||
{ |
|||
if (encoder.Mode == TiffEncodingMode.Rgb || encoder.Mode == TiffEncodingMode.Gray || encoder.Mode == TiffEncodingMode.ColorPalette) |
|||
{ |
|||
var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal }; |
|||
|
|||
this.collector.Add(predictor); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static uint GetSamplesPerPixel(TiffEncoderCore encoder) |
|||
{ |
|||
switch (encoder.PhotometricInterpretation) |
|||
{ |
|||
case TiffPhotometricInterpretation.Rgb: |
|||
return 3; |
|||
case TiffPhotometricInterpretation.PaletteColor: |
|||
case TiffPhotometricInterpretation.BlackIsZero: |
|||
case TiffPhotometricInterpretation.WhiteIsZero: |
|||
return 1; |
|||
default: |
|||
return 3; |
|||
} |
|||
} |
|||
|
|||
private static ushort[] GetBitsPerSampleValue(TiffEncoderCore encoder) |
|||
{ |
|||
switch (encoder.PhotometricInterpretation) |
|||
{ |
|||
case TiffPhotometricInterpretation.PaletteColor: |
|||
return new ushort[] { 8 }; |
|||
case TiffPhotometricInterpretation.Rgb: |
|||
return new ushort[] { 8, 8, 8 }; |
|||
case TiffPhotometricInterpretation.WhiteIsZero: |
|||
if (encoder.Mode == TiffEncodingMode.BiColor) |
|||
{ |
|||
return new ushort[] { 1 }; |
|||
} |
|||
|
|||
return new ushort[] { 8 }; |
|||
case TiffPhotometricInterpretation.BlackIsZero: |
|||
if (encoder.Mode == TiffEncodingMode.BiColor) |
|||
{ |
|||
return new ushort[] { 1 }; |
|||
} |
|||
|
|||
return new ushort[] { 8 }; |
|||
default: |
|||
return new ushort[] { 8, 8, 8 }; |
|||
} |
|||
} |
|||
|
|||
private static ushort GetCompressionType(TiffEncoderCore encoder) |
|||
{ |
|||
switch (encoder.CompressionType) |
|||
{ |
|||
case TiffEncoderCompression.Deflate: |
|||
// Deflate is allowed for all modes.
|
|||
return (ushort)TiffCompression.Deflate; |
|||
case TiffEncoderCompression.PackBits: |
|||
// PackBits is allowed for all modes.
|
|||
return (ushort)TiffCompression.PackBits; |
|||
case TiffEncoderCompression.Lzw: |
|||
if (encoder.Mode == TiffEncodingMode.Rgb || encoder.Mode == TiffEncodingMode.Gray || encoder.Mode == TiffEncodingMode.ColorPalette) |
|||
{ |
|||
return (ushort)TiffCompression.Lzw; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case TiffEncoderCompression.CcittGroup3Fax: |
|||
if (encoder.Mode == TiffEncodingMode.BiColor) |
|||
{ |
|||
return (ushort)TiffCompression.CcittGroup3Fax; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case TiffEncoderCompression.ModifiedHuffman: |
|||
if (encoder.Mode == TiffEncodingMode.BiColor) |
|||
{ |
|||
return (ushort)TiffCompression.Ccitt1D; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
return (ushort)TiffCompression.None; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,201 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Linq; |
|||
|
|||
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// The tiff metadata extensions
|
|||
/// </summary>
|
|||
internal static class TiffFrameMetadataExtensions |
|||
{ |
|||
public static T[] GetArray<T>(this TiffFrameMetadata meta, ExifTag tag, bool optional = false) |
|||
where T : struct |
|||
{ |
|||
if (meta.TryGetArray(tag, out T[] result)) |
|||
{ |
|||
return result; |
|||
} |
|||
|
|||
if (!optional) |
|||
{ |
|||
TiffThrowHelper.ThrowTagNotFound(nameof(tag)); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public static bool TryGetArray<T>(this TiffFrameMetadata meta, ExifTag tag, out T[] result) |
|||
where T : struct |
|||
{ |
|||
foreach (IExifValue entry in meta.FrameTags) |
|||
{ |
|||
if (entry.Tag == tag) |
|||
{ |
|||
DebugGuard.IsTrue(entry.IsArray, "Expected array entry"); |
|||
|
|||
result = (T[])entry.GetValue(); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
result = null; |
|||
return false; |
|||
} |
|||
|
|||
public static TEnum[] GetEnumArray<TEnum, TTagValue>(this TiffFrameMetadata meta, ExifTag tag, bool optional = false) |
|||
where TEnum : struct |
|||
where TTagValue : struct |
|||
{ |
|||
if (meta.TryGetArray(tag, out TTagValue[] result)) |
|||
{ |
|||
// todo: improve
|
|||
return result.Select(a => (TEnum)(object)a).ToArray(); |
|||
} |
|||
|
|||
if (!optional) |
|||
{ |
|||
TiffThrowHelper.ThrowTagNotFound(nameof(tag)); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public static string GetString(this TiffFrameMetadata meta, ExifTag tag) |
|||
{ |
|||
foreach (IExifValue entry in meta.FrameTags) |
|||
{ |
|||
if (entry.Tag == tag) |
|||
{ |
|||
DebugGuard.IsTrue(entry.DataType == ExifDataType.Ascii, "Expected string entry"); |
|||
object value = entry.GetValue(); |
|||
DebugGuard.IsTrue(value is string, "Expected string entry"); |
|||
|
|||
return (string)value; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public static bool SetString(this TiffFrameMetadata meta, ExifTag tag, string value) |
|||
{ |
|||
IExifValue obj = FindOrCreate(meta, tag); |
|||
DebugGuard.IsTrue(obj.DataType == ExifDataType.Ascii, "Expected string entry"); |
|||
|
|||
return obj.TrySetValue(value); |
|||
} |
|||
|
|||
public static TEnum? GetSingleEnumNullable<TEnum, TTagValue>(this TiffFrameMetadata meta, ExifTag tag) |
|||
where TEnum : struct |
|||
where TTagValue : struct |
|||
{ |
|||
if (!meta.TryGetSingle(tag, out TTagValue value)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return (TEnum)(object)value; |
|||
} |
|||
|
|||
public static TEnum GetSingleEnum<TEnum, TTagValue>(this TiffFrameMetadata meta, ExifTag tag, TEnum? defaultValue = null) |
|||
where TEnum : struct |
|||
where TTagValue : struct |
|||
=> meta.GetSingleEnumNullable<TEnum, TTagValue>(tag) ?? (defaultValue != null ? defaultValue.Value : throw TiffThrowHelper.TagNotFound(nameof(tag))); |
|||
|
|||
public static bool SetSingleEnum<TEnum, TTagValue>(this TiffFrameMetadata meta, ExifTag tag, TEnum value) |
|||
where TEnum : struct |
|||
where TTagValue : struct |
|||
{ |
|||
IExifValue obj = FindOrCreate(meta, tag); |
|||
|
|||
object val = (TTagValue)(object)value; |
|||
return obj.TrySetValue(val); |
|||
} |
|||
|
|||
public static T GetSingle<T>(this TiffFrameMetadata meta, ExifTag tag) |
|||
where T : struct |
|||
{ |
|||
if (meta.TryGetSingle(tag, out T result)) |
|||
{ |
|||
return result; |
|||
} |
|||
|
|||
throw TiffThrowHelper.TagNotFound(nameof(tag)); |
|||
} |
|||
|
|||
public static bool TryGetSingle<T>(this TiffFrameMetadata meta, ExifTag tag, out T result) |
|||
where T : struct |
|||
{ |
|||
foreach (IExifValue entry in meta.FrameTags) |
|||
{ |
|||
if (entry.Tag == tag) |
|||
{ |
|||
DebugGuard.IsTrue(!entry.IsArray, "Expected non array entry"); |
|||
|
|||
object value = entry.GetValue(); |
|||
|
|||
result = (T)value; |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
result = default; |
|||
return false; |
|||
} |
|||
|
|||
public static bool SetSingle<T>(this TiffFrameMetadata meta, ExifTag tag, T value) |
|||
where T : struct |
|||
{ |
|||
IExifValue obj = FindOrCreate(meta, tag); |
|||
DebugGuard.IsTrue(!obj.IsArray, "Expected non array entry"); |
|||
|
|||
object val = (T)(object)value; |
|||
return obj.TrySetValue(val); |
|||
} |
|||
|
|||
public static bool Remove(this TiffFrameMetadata meta, ExifTag tag) |
|||
{ |
|||
IExifValue obj = null; |
|||
foreach (IExifValue entry in meta.FrameTags) |
|||
{ |
|||
if (entry.Tag == tag) |
|||
{ |
|||
obj = entry; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (obj != null) |
|||
{ |
|||
return meta.FrameTags.Remove(obj); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
private static IExifValue FindOrCreate(TiffFrameMetadata meta, ExifTag tag) |
|||
{ |
|||
IExifValue obj = null; |
|||
foreach (IExifValue entry in meta.FrameTags) |
|||
{ |
|||
if (entry.Tag == tag) |
|||
{ |
|||
obj = entry; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (obj == null) |
|||
{ |
|||
obj = ExifValues.Create(tag); |
|||
meta.FrameTags.Add(obj); |
|||
} |
|||
|
|||
return obj; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue