using System; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Collections; namespace SpiffLib { /// /// The Misc class encapsulates various static helper methods that lack a more /// appropriate home. /// public class Misc { class Preprocessor { Hashtable m_htbl = new Hashtable(); public Preprocessor(Stream stmInclude) { // Parse stmInclude and make a hash table Regex rexI = new Regex(@"^\#define\s*(?\w+)\s*(?[\-0-9]+).*$"); stmInclude.Seek(0, SeekOrigin.Begin); TextReader tr = new StreamReader(stmInclude); while (true) { string strT = tr.ReadLine(); if (strT == null) break; // Remove white space strT.Trim(); if (strT.Length == 0) continue; // Comment? if (strT.StartsWith("//")) continue; // Match against regex Match mat = rexI.Match(strT); string strName = mat.Groups["name"].Value; int nValue = Int32.Parse(mat.Groups["value"].Value); if (strName.Length != 0) m_htbl.Add(strName, nValue); } } public MemoryStream Preprocess(Stream stmContent) { // Go through stmContent and replace matches with values stmContent.Seek(0, SeekOrigin.Begin); TextReader tr = new StreamReader(stmContent); MemoryStream stm = new MemoryStream(); TextWriter tw = new StreamWriter(stm); while (true) { string strT = tr.ReadLine(); if (strT == null) break; // Parse for alpha numeric sequences string strResult = Regex.Replace(strT, @"\w+", new MatchEvaluator(ReplaceText)); tw.WriteLine(strResult); } tw.Flush(); return stm; } string ReplaceText(Match mat) { string str = mat.ToString(); if (!m_htbl.ContainsKey(str)) return str; return ((int)m_htbl[str]).ToString(); } } public static MemoryStream PreprocessStream(Stream stmContent, Stream stmInclude) { Preprocessor pp = new Preprocessor(stmInclude); return pp.Preprocess(stmContent); } public static byte[] GetByteArrayFromString(string str) { byte[] ab = new byte[str.Length + 1]; for (int n = 0; n < str.Length; n++) ab[n] = (byte)str[n]; ab[str.Length] = 0; return ab; } /// /// Takes RGB values as floats in the range from 0 to 1 and returns /// HSL values also in the range from 0 to 1. /// /// Thank you Ken Fishkin of Pixar Inc and Graphics Gems /// /// /// /// /// /// /// public static unsafe void RgbToHsl(float r, float g, float b, float *h, float *s, float *l) { float v; float m; float vm; float r2, g2, b2; v = Math.Max(r,g); v = Math.Max(v,b); m = Math.Min(r,g); m = Math.Min(m,b); *h = 0; *s = 0; if ((*l = (m + v) / 2.0f) <= 0.0f) return; if ((*s = vm = v - m) > 0.0f) { *s /= (*l <= 0.5f) ? (v + m ) : (2.0f - v - m) ; } else return; r2 = (v - r) / vm; g2 = (v - g) / vm; b2 = (v - b) / vm; if (r == v) *h = (g == m ? 5.0f + b2 : 1.0f - g2); else if (g == v) *h = (b == m ? 1.0f + r2 : 3.0f - b2); else *h = (r == m ? 3.0f + g2 : 5.0f - r2); *h /= 6; } /// /// Takes HSL values as floats in the range from 0 to 1 and returns /// RGB values also in the range from 0 to 1. /// /// Thank you Ken Fishkin of Pixar Inc and Graphics Gems /// /// /// /// /// /// /// public static unsafe void HslToRgb(float h, float s, float l, float *r, float *g, float *b) { float v; v = (l <= 0.5f) ? (l * (1.0f + s)) : (l + s - l * s); if (v <= 0) { *r = *g = *b = 0.0f; } else { float m; float sv; int sextant; float fract, vsf, mid1, mid2; m = l + l - v; sv = (v - m ) / v; h *= 6.0f; sextant = (int)h; fract = h - sextant; vsf = v * sv * fract; mid1 = m + vsf; mid2 = v - vsf; switch (sextant) { case 0: *r = v; *g = mid1; *b = m; break; case 1: *r = mid2; *g = v; *b = m; break; case 2: *r = m; *g = v; *b = mid1; break; case 3: *r = m; *g = mid2; *b = v; break; case 4: *r = mid1; *g = m; *b = v; break; case 5: *r = v; *g = m; *b = mid2; break; } } } /// /// All this routine does is create a duplicate of the passed-in bitmap. Its utility /// comes from the fact that it doesn't duplicate the properties (e.g., horizontal /// and vertical resolution) that are attached to bitmaps after they've been read /// from files. These properties can interfere with the expected operation of various /// Bitmap/Graphics methods (e.g., DragImage will inexplicably scale the bitmap when /// drawn from based on its intrinsic resolution properties). We would prefer to just /// clear out these properties or force them to be ignored but all such attempts to /// date have been rebuffed. /// /// The Bitmap to be 'normalized' /// A property-free Bitmap public static unsafe Bitmap NormalizeBitmap(Bitmap bmSrc) { Bitmap bmDst = new Bitmap(bmSrc.Width, bmSrc.Height, bmSrc.PixelFormat); Rectangle rc = new Rectangle(0, 0, bmSrc.Width, bmSrc.Height); BitmapData bmdSrc = bmSrc.LockBits(rc, ImageLockMode.ReadOnly, bmSrc.PixelFormat); BitmapData bmdDst = bmDst.LockBits(rc, ImageLockMode.WriteOnly, bmSrc.PixelFormat); int cul = bmdSrc.Height * bmdSrc.Stride / sizeof(ulong); ulong *pulSrc = (ulong *)bmdSrc.Scan0; ulong *pulDst = (ulong *)bmdDst.Scan0; while (cul-- > 0) { *pulDst++ = *pulSrc++; } bmSrc.UnlockBits(bmdSrc); bmDst.UnlockBits(bmdDst); return bmDst; } static Color s_clrShadow = Color.FromArgb(156, 212, 248); static Color s_clrTransparent = Color.FromArgb(255, 0, 255); /// /// Scan from the bottom up. First scanline containing non-shadow, non-transparent color /// is the baseline. /// /// /// A scanline offset from the top of the image public static unsafe int FindBaseline(Bitmap bm) { Rectangle rc = new Rectangle(0, 0, bm.Width, bm.Height); BitmapData bmd = bm.LockBits(rc, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); byte *pbBase = (byte *)bmd.Scan0; for (int y = bm.Height - 1; y >= 0; y--) { byte *pb = pbBase + y * bmd.Stride; for (int x = 0; x < bm.Width; x++, pb += 3) { Color clr = Color.FromArgb(pb[2], pb[1], pb[0]); if (clr != s_clrTransparent && clr != s_clrShadow) { bm.UnlockBits(bmd); return y; } } } bm.UnlockBits(bmd); return 0; } /// /// The SwapUShort method swaps the high and low bytes of the passed ushort /// value. It's good for converting between big and little-endian ushort values. /// /// The ushort value whose bytes to swap /// The byte-swapped ushort public static ushort SwapUShort(ushort us) { int n = (int)us; return (ushort)(((n >> 8) & 0x00ff) | ((n << 8) & 0xff00)); } /// /// The SwapUInt method swaps the endian order of a 4 byte type. /// /// The uint value whose bytes to swap /// The byte-swapped uint public static uint SwapUInt(uint ui) { return ((((ui)&0xFF)<<24) | (((ui)&0xFF00)<<8) | (((ui)&0xFF0000)>>8) | (((ui)&0xFF000000)>>24)); } /// /// This cool method returns a new bitmap which has a copy of the passed-in bitmap /// surrounded by a colored outline. /// /// Bitmap to outline /// width/height of outline in pixels /// color of the outline /// Bitmap with traced edges public static Bitmap TraceEdges(Bitmap bm, int cp, Color clrEdge) { // Create a mask of sorts Color clrTransparent = Color.FromArgb(255, 0, 255); Bitmap bmEdge = new Bitmap(bm.Width, bm.Height); Graphics gEdge = Graphics.FromImage(bmEdge); gEdge.Clear(clrTransparent); gEdge.DrawImage(bm, 0, 0); for (int x = 0; x < bmEdge.Width; x++) { for (int y = 0; y < bmEdge.Height; y++) { Color clrT = bmEdge.GetPixel(x, y); if (clrT != clrTransparent) { bmEdge.SetPixel(x, y, clrEdge); } } } bmEdge.MakeTransparent(clrTransparent); // Draw the mask aligned to create edges Bitmap bmT = new Bitmap(bm.Width + cp * 2, bm.Height + cp * 2); Graphics gT = Graphics.FromImage(bmT); gT.Clear(clrTransparent); gT.DrawImage(bmEdge, 0, 0); gT.DrawImage(bmEdge, cp, 0); gT.DrawImage(bmEdge, cp * 2, 0); gT.DrawImage(bmEdge, cp * 2, cp); gT.DrawImage(bmEdge, cp * 2, cp * 2); gT.DrawImage(bmEdge, cp, cp * 2); gT.DrawImage(bmEdge, 0, cp * 2); gT.DrawImage(bmEdge, 0, cp); // Draw the actual image in the middle; we're done gT.DrawImage(bm, cp, cp); bmT.MakeTransparent(clrTransparent); // Force cleanup gEdge.Dispose(); gT.Dispose(); return bmT; } /// /// The GetPropertyItemValue method takes a instance (usually /// acquired via Image.PropertyItems[]) and returns its value as a (boxed) /// CLR type. /// /// /// /// string strCopyright = "no copyright specified"; /// Bitmap bm = new Bitmap("pict.jpg"); /// foreach (PropertyItem prpi in bm.PropertyItems) { /// if ((PropertyTag)prpi.Id == PropertyTag.Copyright) /// strCopyright = Misc.GetPropertyItemValue(prpi); /// } /// bm.Dispose(); /// /// /// The containing the desired Value /// The value, usually a boxed primitive type /// If the of the item is unknown the original /// Value byte array is returned. /// /// /// static public object GetPropertyItemValue(PropertyItem prpi) { switch ((PropertyTagType)prpi.Type) { case PropertyTagType.ASCII: { // UNDONE: a cleaner way to do this? StreamReader stmr = new StreamReader(new MemoryStream(prpi.Value), Encoding.ASCII); return stmr.ReadToEnd(); } case PropertyTagType.Byte: return prpi.Value[0]; case PropertyTagType.Long: return BitConverter.ToUInt32(prpi.Value, 0); case PropertyTagType.Rational: { UInt32 n1 = BitConverter.ToUInt32(prpi.Value, 0); UInt32 n2 = BitConverter.ToUInt32(prpi.Value, 4); return (float)n1 / (float)n2; } case PropertyTagType.Short: return BitConverter.ToUInt16(prpi.Value, 0); case PropertyTagType.SLONG: return BitConverter.ToInt32(prpi.Value, 0); case PropertyTagType.SRational: { Int32 n1 = BitConverter.ToInt32(prpi.Value, 0); Int32 n2 = BitConverter.ToInt32(prpi.Value, 4); return (float)n1 / (float)n2; } } return prpi.Value; } } /// /// This enumeration should be supplied as part of the FCL and used by the PropertyInfo /// class but it isn't. I dug its values from a GDI+ headerfile, GdiPlusImaging.h /// [Serializable] public enum PropertyTagType : short { /// /// /// Byte = 1, /// /// /// ASCII = 2, /// /// /// Short = 3, /// /// /// Long = 4, /// /// /// Rational = 5, /// /// /// Undefined = 7, /// /// /// SLONG = 9, /// /// /// SRational = 10 } /// /// This enumeration should be supplied as part of the FCL and used by the PropertyInfo /// class but it isn't. I dug its values from a GDI+ headerfile, GdiPlusImaging.h /// [Serializable] public enum PropertyTag : int { /// /// /// ExifIFD = 0x8769, /// /// /// GpsIFD = 0x8825, /// /// /// NewSubfileType = 0x00FE, /// /// /// SubfileType = 0x00FF, /// /// /// ImageWidth = 0x0100, /// /// /// ImageHeight = 0x0101, /// /// /// BitsPerSample = 0x0102, /// /// /// Compression = 0x0103, /// /// /// PhotometricInterp = 0x0106, /// /// /// ThreshHolding = 0x0107, /// /// /// CellWidth = 0x0108, /// /// /// CellHeight = 0x0109, /// /// /// FillOrder = 0x010A, /// /// /// DocumentName = 0x010D, /// /// /// ImageDescription = 0x010E, /// /// /// EquipMake = 0x010F, /// /// /// EquipModel = 0x0110, /// /// /// StripOffsets = 0x0111, /// /// /// Orientation = 0x0112, /// /// /// SamplesPerPixel = 0x0115, /// /// /// RowsPerStrip = 0x0116, /// /// /// StripBytesCount = 0x0117, /// /// /// MinSampleValue = 0x0118, /// /// /// MaxSampleValue = 0x0119, /// /// Image resolution in width direction /// XResolution = 0x011A, /// /// Image resolution in height direction /// YResolution = 0x011B, /// /// Image data arrangement /// PlanarConfig = 0x011C, /// /// /// PageName = 0x011D, /// /// /// XPosition = 0x011E, /// /// /// YPosition = 0x011F, /// /// /// FreeOffset = 0x0120, /// /// /// FreeByteCounts = 0x0121, /// /// /// GrayResponseUnit = 0x0122, /// /// /// GrayResponseCurve = 0x0123, /// /// /// T4Option = 0x0124, /// /// /// T6Option = 0x0125, /// /// Unit of X and Y resolution /// ResolutionUnit = 0x0128, /// /// /// PageNumber = 0x0129, /// /// /// TransferFuncition = 0x012D, /// /// /// SoftwareUsed = 0x0131, /// /// /// DateTime = 0x0132, /// /// /// Artist = 0x013B, /// /// /// HostComputer = 0x013C, /// /// /// Predictor = 0x013D, /// /// /// WhitePoint = 0x013E, /// /// /// PrimaryChromaticities = 0x013F, /// /// /// ColorMap = 0x0140, /// /// /// HalftoneHints = 0x0141, /// /// /// TileWidth = 0x0142, /// /// /// TileLength = 0x0143, /// /// /// TileOffset = 0x0144, /// /// /// TileByteCounts = 0x0145, /// /// /// InkSet = 0x014C, /// /// /// InkNames = 0x014D, /// /// /// NumberOfInks = 0x014E, /// /// /// DotRange = 0x0150, /// /// /// TargetPrinter = 0x0151, /// /// /// ExtraSamples = 0x0152, /// /// /// SampleFormat = 0x0153, /// /// /// SMinSampleValue = 0x0154, /// /// /// SMaxSampleValue = 0x0155, /// /// /// TransferRange = 0x0156, /// /// /// JPEGProc = 0x0200, /// /// /// JPEGInterFormat = 0x0201, /// /// /// JPEGInterLength = 0x0202, /// /// /// JPEGRestartInterval = 0x0203, /// /// /// JPEGLosslessPredictors = 0x0205, /// /// /// JPEGPointTransforms = 0x0206, /// /// /// JPEGQTables = 0x0207, /// /// /// JPEGDCTables = 0x0208, /// /// /// JPEGACTables = 0x0209, /// /// /// YCbCrCoefficients = 0x0211, /// /// /// YCbCrSubsampling = 0x0212, /// /// /// YCbCrPositioning = 0x0213, /// /// /// REFBlackWhite = 0x0214, /// /// This TAG is defined by ICC for embedded ICC in TIFF /// ICCProfile = 0x8773, /// /// This TAG is defined by ICC for embedded ICC in TIFF /// Gamma = 0x0301, /// /// This TAG is defined by ICC for embedded ICC in TIFF /// ICCProfileDescriptor = 0x0302, /// /// This TAG is defined by ICC for embedded ICC in TIFF /// SRGBRenderingIntent = 0x0303, /// /// /// ImageTitle = 0x0320, /// /// /// Copyright = 0x8298, // Extra TAGs (Like Adobe Image Information tags etc.) /// /// /// ResolutionXUnit = 0x5001, /// /// /// ResolutionYUnit = 0x5002, /// /// /// ResolutionXLengthUnit = 0x5003, /// /// /// ResolutionYLengthUnit = 0x5004, /// /// /// PrintFlags = 0x5005, /// /// /// PrintFlagsVersion = 0x5006, /// /// /// PrintFlagsCrop = 0x5007, /// /// /// PrintFlagsBleedWidth = 0x5008, /// /// /// PrintFlagsBleedWidthScale = 0x5009, /// /// /// HalftoneLPI = 0x500A, /// /// /// HalftoneLPIUnit = 0x500B, /// /// /// HalftoneDegree = 0x500C, /// /// /// HalftoneShape = 0x500D, /// /// /// HalftoneMisc = 0x500E, /// /// /// HalftoneScreen = 0x500F, /// /// /// JPEGQuality = 0x5010, /// /// /// GridSize = 0x5011, /// /// 1 = JPEG, 0 = RAW RGB /// ThumbnailFormat = 0x5012, /// /// /// ThumbnailWidth = 0x5013, /// /// /// ThumbnailHeight = 0x5014, /// /// /// ThumbnailColorDepth = 0x5015, /// /// /// ThumbnailPlanes = 0x5016, /// /// /// ThumbnailRawBytes = 0x5017, /// /// /// ThumbnailSize = 0x5018, /// /// /// ThumbnailCompressedSize = 0x5019, /// /// /// ColorTransferFunction = 0x501A, /// /// RAW thumbnail bits in JPEG format or RGB format depends on PropertyTagThumbnailFormat /// ThumbnailData = 0x501B, // Thumbnail related TAGs /// /// Thumbnail width /// ThumbnailImageWidth = 0x5020, /// /// Thumbnail height /// ThumbnailImageHeight = 0x5021, /// /// Number of bits per component /// ThumbnailBitsPerSample = 0x5022, /// /// Compression Scheme /// ThumbnailCompression = 0x5023, /// /// Pixel composition /// ThumbnailPhotometricInterp = 0x5024, /// /// Image Tile /// ThumbnailImageDescription = 0x5025, /// /// Manufacturer of Image /// ThumbnailEquipMake = 0x5026, // Input equipment /// /// Model of Image input equipment /// ThumbnailEquipModel = 0x5027, /// /// Image data location /// ThumbnailStripOffsets = 0x5028, /// /// Orientation of image /// ThumbnailOrientation = 0x5029, /// /// Number of components /// ThumbnailSamplesPerPixel = 0x502A, /// /// Number of rows per strip /// ThumbnailRowsPerStrip = 0x502B, /// /// Bytes per compressed strip /// ThumbnailStripBytesCount = 0x502C, /// /// Resolution in width direction /// ThumbnailResolutionX = 0x502D, /// /// Resolution in height direction /// ThumbnailResolutionY = 0x502E, /// /// Image data arrangement /// ThumbnailPlanarConfig = 0x502F, /// /// Unit of X and Y resolution /// ThumbnailResolutionUnit = 0x5030, /// /// Transfer function /// ThumbnailTransferFunction = 0x5031, /// /// Software used /// ThumbnailSoftwareUsed = 0x5032, /// /// File change date and time /// ThumbnailDateTime = 0x5033, /// /// Person who created the image /// ThumbnailArtist = 0x5034, /// /// White point chromaticity /// ThumbnailWhitePoint = 0x5035, /// /// Chromaticities of primaries /// ThumbnailPrimaryChromaticities = 0x5036, /// /// Color space transformaion coefficients /// ThumbnailYCbCrCoefficients = 0x5037, /// /// Subsampling ratio of Y to C /// ThumbnailYCbCrSubsampling = 0x5038, /// /// Y and C position /// ThumbnailYCbCrPositioning = 0x5039, /// /// Pair of black and white reference values /// ThumbnailRefBlackWhite = 0x503A, /// /// Copyright holder /// ThumbnailCopyRight = 0x503B, /// /// /// LuminanceTable = 0x5090, /// /// /// ChrominanceTable = 0x5091, /// /// /// FrameDelay = 0x5100, /// /// /// LoopCount = 0x5101, /// /// Unit specifier for pixel/unit /// PixelUnit = 0x5110, /// /// Pixels per unit in X /// PixelPerUnitX = 0x5111, /// /// Pixels per unit in Y /// PixelPerUnitY = 0x5112, /// /// Palette histogram /// PaletteHistogram = 0x5113, // EXIF specific tag /// /// /// ExifExposureTime = 0x829A, /// /// /// ExifFNumber = 0x829D, /// /// /// ExifExposureProg = 0x8822, /// /// /// ExifSpectralSense = 0x8824, /// /// /// ExifISOSpeed = 0x8827, /// /// /// ExifOECF = 0x8828, /// /// /// ExifVer = 0x9000, /// /// Date and time of original /// ExifDTOrig = 0x9003, /// /// Date and time of digital data generation /// ExifDTDigitized = 0x9004, /// /// /// ExifCompConfig = 0x9101, /// /// /// ExifCompBPP = 0x9102, /// /// /// ExifShutterSpeed = 0x9201, /// /// /// ExifAperture = 0x9202, /// /// /// ExifBrightness = 0x9203, /// /// /// ExifExposureBias = 0x9204, /// /// /// ExifMaxAperture = 0x9205, /// /// /// ExifSubjectDist = 0x9206, /// /// /// ExifMeteringMode = 0x9207, /// /// /// ExifLightSource = 0x9208, /// /// /// ExifFlash = 0x9209, /// /// /// ExifFocalLength = 0x920A, /// /// /// ExifMakerNote = 0x927C, /// /// /// ExifUserComment = 0x9286, /// /// Date and Time subseconds /// ExifDTSubsec = 0x9290, /// /// Date and Time original subseconds /// ExifDTOrigSS = 0x9291, /// /// Date and TIme digitized subseconds /// ExifDTDigSS = 0x9292, /// /// /// ExifFPXVer = 0xA000, /// /// /// ExifColorSpace = 0xA001, /// /// /// ExifPixXDim = 0xA002, /// /// /// ExifPixYDim = 0xA003, /// /// related sound file /// ExifRelatedWav = 0xA004, /// /// /// ExifInterop = 0xA005, /// /// /// ExifFlashEnergy = 0xA20B, /// /// Spatial Frequency Response /// ExifSpatialFR = 0xA20C, /// /// Focal Plane X Resolution /// ExifFocalXRes = 0xA20E, /// /// Focal Plane Y Resolution /// ExifFocalYRes = 0xA20F, /// /// Focal Plane Resolution Unit /// ExifFocalResUnit = 0xA210, /// /// /// ExifSubjectLoc = 0xA214, /// /// /// ExifExposureIndex = 0xA215, /// /// /// ExifSensingMethod = 0xA217, /// /// /// ExifFileSource = 0xA300, /// /// /// ExifSceneType = 0xA301, /// /// /// ExifCfaPattern = 0xA302, /// /// /// GpsVer = 0x0000, /// /// /// GpsLatitudeRef = 0x0001, /// /// /// /// /// /// GpsLatitude = 0x0002, /// /// /// GpsLongitudeRef = 0x0003, /// /// /// GpsLongitude = 0x0004, /// /// /// GpsAltitudeRef = 0x0005, /// /// /// GpsAltitude = 0x0006, /// /// /// GpsGpsTime = 0x0007, /// /// /// GpsGpsSatellites = 0x0008, /// /// /// GpsGpsStatus = 0x0009, /// /// /// GpsGpsMeasureMode = 0x000A, /// /// Measurement precision /// GpsGpsDop = 0x000B, /// /// /// GpsSpeedRef = 0x000C, /// /// /// GpsSpeed = 0x000D, /// /// /// GpsTrackRef = 0x000E, /// /// /// GpsTrack = 0x000F, /// /// /// GpsImgDirRef = 0x0010, /// /// /// GpsImgDir = 0x0011, /// /// /// GpsMapDatum = 0x0012, /// /// /// GpsDestLatRef = 0x0013, /// /// /// GpsDestLat = 0x0014, /// /// /// GpsDestLongRef = 0x0015, /// /// /// GpsDestLong = 0x0016, /// /// /// GpsDestBearRef = 0x0017, /// /// /// GpsDestBear = 0x0018, /// /// /// GpsDestDistRef = 0x0019, /// /// /// GpsDestDist = 0x001A, } }