unit mdhGraphics; { [mdhGraphics] [1.1] Delphi 2005 May 2006 LICENSE The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at "http://www.mozilla.org/MPL/" Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is "[mdhGraphics.pas]". The Initial Developer of the Original Code is Martin Holmes (Victoria, BC, Canada, "http://www.mholmes.com/"). Copyright (C) 2005 Martin Holmes and the University of Victoria Computing and Media Centre. The code was co-developed for university and personal projects, and rights are shared by Martin Holmes and the University of Victoria. All Rights Reserved. } { Written by Martin Holmes, October 2005. This is a utility library containing functions relating to graphics, especially the conversion of HTML colours to Windows colours and vice-versa. Most functions here were abstracted from older libraries developed for the Hot Potatoes, Quandary, TexToys and Markin programs. Others are utility functions based on the Graphics32 libraries. Dependencies: Graphics32 () } interface uses Windows, Classes, Graphics, SysUtils, Controls, Forms, GR32, GR32_Image, FileCtrl, GR32_Layers, GR32_RangeBars, GR32_Filters, GR32_Transforms, GR32_Resamplers, jpeg; function WebColorToWinColor(InColor: string): TColor; function WebColorToWinColorDef(InColor: string; DefColor: TColor): TColor; function ColorToHTML(InColor: TColor; AddHash: Boolean): string; function NormalizeWebColor(InColor: string; MakeHex: Boolean): string; {This function loads an image from disk, scales it to match the target width, then saves it to FilePath, returning the ScaleFactor used and the OutputDimensions to the calling function. Its primary use is to create a smaller version of an image suitable for use on a Web page.} function SaveScaledImage(TargetWidth: integer; InputFile, OutputFile: WideString; var ScaleFactor: Double; var OutputDimensions: TPoint): Boolean; {This function loads an image from disk, scales the specified region of it to match the target width, then saves it to FilePath, returning the ScaleFactor used and the OutputDimensions to the calling function. Its primary use is to create a smaller version of part of an image suitable for use on a Web page, perhaps as a thumbnail.} function SaveScaledImageRegion(TargetWidth: integer; InputFile, OutputFile: WideString; RegionX, RegionY, RegionW, RegionH: integer; var ScaleFactor: Double; var OutputDimensions: TPoint): Boolean; function SaveResizedImageRegion(TargetWidth, TargetHeight: integer; InputFile, OutputFile: WideString; RegionX, RegionY, RegionW, RegionH: integer): Boolean; const ImageExtensions: WideString = '.gif.cur.pcx.ani.jpg.jpeg.bmp.ico.emf.wmf.tif.tiff'; var NSC: TStringList; implementation function NormalizeWebColor(InColor: string; MakeHex: Boolean): string; var i: integer; const HexDigits = ['0'..'9','a'..'f','A'..'F']; begin Result := InColor; //default InColor := Trim(InColor); if Length(InColor) < 1 then Exit; //Check it against the named Colors table if NSC.Values[InColor] <> '' then begin if MakeHex = True then Result := NSC.Values[InColor] else Result := InColor; Exit; end; //Add a hash if there isn't one if InColor[1] <> '#' then InColor := '#' + InColor; //Make it the right length if Length(InColor) > 7 then InColor := Copy(InColor, 1, 7); while Length(InColor) < 7 do InColor := InColor + '9'; //Make each relevant digit correct for i := 2 to 7 do if not (InColor[i] in HexDigits) then InColor[i] := '9'; Result := InColor; end; function WebColorToWinColor(InColor: string): TColor; begin //First set default Result := clGray; //Normalize to a hex Color InColor := NormalizeWebColor(InColor, True); //Discard the initial hash InColor := Copy(InColor, 2, 6); //Reorder the components InColor := '$' + Copy(InColor, 5, 2) + Copy(InColor, 3, 2) + Copy(InColor, 1, 2); //Convert to Color Result := StrToIntDef(InColor, $00CCCCCC); end; function WebColorToWinColorDef(InColor: string; DefColor: TColor): TColor; begin //First set default Result := DefColor; //Normalize to a hex Color InColor := NormalizeWebColor(InColor, True); //Discard the initial hash InColor := Copy(InColor, 2, 6); //Reorder the components InColor := '$' + Copy(InColor, 5, 2) + Copy(InColor, 3, 2) + Copy(InColor, 1, 2); //Convert to Color Result := StrToIntDef(InColor, DefColor); end; function ColorToHTML(InColor: TColor; AddHash: Boolean): string; var TheRgbValue: TColorRef; begin TheRgbValue := ColorToRGB(InColor); Result := Format('%.2x%.2x%.2x', [GetRValue(TheRGBValue), GetGValue(TheRGBValue), GetBValue(TheRGBValue)]); if AddHash = True then Result := '#' + LowerCase(Result); end; function SaveScaledImage(TargetWidth: integer; InputFile, OutputFile: WideString; var ScaleFactor: Double; var OutputDimensions: TPoint): Boolean; var OrigBitmap: TBitmap32; ScaledBitmap: TBitmap32; BmpOut: TBitmap; JPGOut: TJPEGImage; OrigCursor: TCursor; begin ScaleFactor := 1; //default Result := False; //default OrigCursor := Screen.Cursor; try Screen.Cursor := crHourglass; try JPGOut := TJPEGImage.Create; try BmpOut := TBitmap.Create; try OrigBitmap := TBitmap32.Create; try //New for 1.6: specify a higher quality resampler. OrigBitmap.ResamplerClassName := 'TKernelResampler'; //Load the current image file into the new bitmap OrigBitmap.LoadFromFile(InputFile); //Figure out the correct scaling: don't expand, just reduce if OrigBitmap.Width > TargetWidth then begin ScaleFactor := TargetWidth / OrigBitmap.Width; //Scale the image ScaledBitmap := TBitmap32.Create; try ScaledBitmap.SetSize(TargetWidth, Round(OrigBitmap.Height * ScaleFactor)); //Return the resulting width and height for the convenience of the calling function OutputDimensions.X := ScaledBitmap.Width; OutputDimensions.Y := ScaledBitmap.Height; StretchTransfer(ScaledBitmap, Rect(0, 0, ScaledBitmap.Width, ScaledBitmap.Height), Rect(0, 0, ScaledBitmap.Width, ScaledBitmap.Height), OrigBitmap, Rect(0, 0, OrigBitmap.Width, OrigBitmap.Height), OrigBitmap.Resampler, dmOpaque, nil); {OrigBitmap.DrawMode := dmOpaque; OrigBitmap.DrawTo(ScaledBitmap, Rect(0, 0, ScaledBitmap.Width, ScaledBitmap.Height)); } BmpOut.Assign(ScaledBitmap); finally FreeAndNil(ScaledBitmap); end; end else begin BmpOut.Assign(OrigBitmap); ScaleFactor := 1; OutputDimensions.X := OrigBitmap.Width; OutputDimensions.Y := OrigBitmap.Height; end; //Save it to the right location JPGOut.CompressionQuality := 100; JPGOut.ProgressiveEncoding := True; JPGOut.Assign(BmpOut); //Enforce extension just in case JPGOut.SaveToFile(ChangeFileExt(OutputFile, '.jpg')); Result := True; finally FreeAndNil(OrigBitmap); end; finally FreeAndNil(BmpOut); end; finally FreeAndNil(JPGOut); end; finally Screen.Cursor := OrigCursor; end; except //Returning false is enough end; end; function SaveScaledImageRegion(TargetWidth: integer; InputFile, OutputFile: WideString; RegionX, RegionY, RegionW, RegionH: integer; var ScaleFactor: Double; var OutputDimensions: TPoint): Boolean; var OrigBitmap: TBitmap32; TransformedBitmap: TBitmap32; BmpOut: TBitmap; JPGOut: TJPEGImage; OrigCursor: TCursor; begin ScaleFactor := 1; //default Result := False; //default //Sanity check if (TargetWidth < 1) then Exit; if (RegionW < 1) or (RegionH < 1) then Exit; OrigCursor := Screen.Cursor; try Screen.Cursor := crHourglass; try JPGOut := TJPEGImage.Create; try BmpOut := TBitmap.Create; try OrigBitmap := TBitmap32.Create; try TransformedBitmap := TBitmap32.Create; try OrigBitmap.ResamplerClassName := 'TKernelResampler'; //Load the current image file into the new bitmap OrigBitmap.LoadFromFile(InputFile); //More sanity checks if ((RegionX + RegionW) > OrigBitmap.Width) or ((RegionY + RegionH) > OrigBitmap.Height) then Exit; //Figure out the correct scaling: don't expand, just reduce if RegionW > TargetWidth then begin ScaleFactor := TargetWidth / RegionW; //Scale the image TransformedBitmap.SetSize(TargetWidth, Round(RegionH * ScaleFactor)); StretchTransfer(TransformedBitmap, Rect(0, 0, TransformedBitmap.Width, TransformedBitmap.Height), Rect(0, 0, TransformedBitmap.Width, TransformedBitmap.Height), OrigBitmap, Rect(RegionX, RegionY, (RegionW+RegionX), (RegionH+RegionY)), OrigBitmap.Resampler, dmOpaque, nil); end else //The target width is the same as the original, or more begin ScaleFactor := 1; TransformedBitmap.SetSize(RegionW, RegionH); BlockTransfer(TransformedBitmap, 0, 0, Rect(0, 0, TransformedBitmap.Width, TransformedBitmap.Height), OrigBitmap, Rect(RegionX, RegionY, (RegionW+RegionX), (RegionH+RegionY)), dmOpaque); end; //Return the resulting width and height for the convenience of the calling function OutputDimensions.X := TransformedBitmap.Width; OutputDimensions.Y := TransformedBitmap.Height; BmpOut.Assign(TransformedBitmap); finally FreeAndNil(TransformedBitmap); end; //Save it to the right location JPGOut.CompressionQuality := 100; JPGOut.ProgressiveEncoding := True; JPGOut.Assign(BmpOut); //Enforce extension just in case JPGOut.SaveToFile(ChangeFileExt(OutputFile, '.jpg')); Result := True; finally FreeAndNil(OrigBitmap); end; finally FreeAndNil(BmpOut); end; finally FreeAndNil(JPGOut); end; finally Screen.Cursor := OrigCursor; end; except //Returning false is enough end; end; function SaveResizedImageRegion(TargetWidth, TargetHeight: integer; InputFile, OutputFile: WideString; RegionX, RegionY, RegionW, RegionH: integer): Boolean; var OrigBitmap: TBitmap32; TransformedBitmap: TBitmap32; BmpOut: TBitmap; JPGOut: TJPEGImage; OrigCursor: TCursor; begin Result := False; //default //Sanity check if (TargetWidth < 1) then Exit; if (RegionW < 1) or (RegionH < 1) then Exit; OrigCursor := Screen.Cursor; try Screen.Cursor := crHourglass; try JPGOut := TJPEGImage.Create; try BmpOut := TBitmap.Create; try OrigBitmap := TBitmap32.Create; try TransformedBitmap := TBitmap32.Create; try OrigBitmap.ResamplerClassName := 'TKernelResampler'; //Load the current image file into the new bitmap OrigBitmap.LoadFromFile(InputFile); //More sanity checks if ((RegionX + RegionW) > OrigBitmap.Width) or ((RegionY + RegionH) > OrigBitmap.Height) then Exit; //Scale the image TransformedBitmap.SetSize(TargetWidth, TargetHeight); StretchTransfer(TransformedBitmap, Rect(0, 0, TransformedBitmap.Width, TransformedBitmap.Height), Rect(0, 0, TransformedBitmap.Width, TransformedBitmap.Height), OrigBitmap, Rect(RegionX, RegionY, (RegionW+RegionX), (RegionH+RegionY)), OrigBitmap.Resampler, dmOpaque, nil); BmpOut.Assign(TransformedBitmap); finally FreeAndNil(TransformedBitmap); end; //Save it to the right location JPGOut.CompressionQuality := 100; JPGOut.ProgressiveEncoding := True; JPGOut.Assign(BmpOut); //Enforce extension just in case JPGOut.SaveToFile(ChangeFileExt(OutputFile, '.jpg')); Result := True; finally FreeAndNil(OrigBitmap); end; finally FreeAndNil(BmpOut); end; finally FreeAndNil(JPGOut); end; finally Screen.Cursor := OrigCursor; end; except //Returning false is enough end; end; initialization NSC := TStringList.Create; NSC.Add('aliceblue=#F0F8FF'); NSC.Add('antiquewhite=#FAEBD7'); NSC.Add('aqua=#00FFFF'); NSC.Add('aquamarine=#7FFFD4'); NSC.Add('azure=#F0FFFF'); NSC.Add('beige=#F5F5DC'); NSC.Add('bisque=#FFE4C4'); NSC.Add('black=#000000'); NSC.Add('blanchedalmond=#FFEBCD'); NSC.Add('blue=#0000FF'); NSC.Add('blueviolet=#8A2BE2'); NSC.Add('brown=#A52A2A'); NSC.Add('burlywood=#DEB887'); NSC.Add('cadetblue=#5F9EA0'); NSC.Add('chartreuse=#7FFF00'); NSC.Add('chocolate=#D2691E'); NSC.Add('coral=#FF7F50'); NSC.Add('cornflowerblue=#6495ED'); NSC.Add('cornsilk=#FFF8DC'); NSC.Add('crimson=#DC1436'); NSC.Add('cyan=#00FFFF'); NSC.Add('darkblue=#00008B'); NSC.Add('darkcyan=#008B8B'); NSC.Add('darkgoldenrod=#B8860B'); NSC.Add('darkgrey=#A9A9A9'); NSC.Add('darkgreen=#006400'); NSC.Add('darkkhaki=#BDB76B'); NSC.Add('darkmagenta=#8B008B'); NSC.Add('darkolivegreen=#556B2F'); NSC.Add('darkorange=#FF8C00'); NSC.Add('darkorchid=#9932CC'); NSC.Add('darkred=#8B0000'); NSC.Add('darksalmon=#E9967A'); NSC.Add('darkseagreen=#8FBC8F'); NSC.Add('darkslateblue=#483D8B'); NSC.Add('darkslategray=#2F4F4F'); NSC.Add('darkturquoise=#00CED1'); NSC.Add('darkviolet=#9400D3'); NSC.Add('deeppink=#FF1493'); NSC.Add('deepskyblue=#00BFFF'); NSC.Add('dimgray=#696969'); NSC.Add('dodgerblue=#1E90FF'); NSC.Add('firebrick=#B22222'); NSC.Add('floralwhite=#FFFAF0'); NSC.Add('forestgreen=#228B22'); NSC.Add('fuchsia=#FF00FF'); NSC.Add('gainsboro=#DCDCDC'); NSC.Add('ghostwhite=#F8F8FF'); NSC.Add('gold=#FFD700'); NSC.Add('goldenrod=#DAA520'); NSC.Add('gray=#808080'); NSC.Add('green=#008000'); NSC.Add('greenyellow=#ADFF2F'); NSC.Add('honeydew=#F0FFF0'); NSC.Add('hotpink=#FF69B4'); NSC.Add('indianred=#CD5C5C'); NSC.Add('indigo=#4B0082'); NSC.Add('ivory=#FFFFF0'); NSC.Add('khaki=#F0E68C'); NSC.Add('lavender=#E6E6FA'); NSC.Add('lavenderblush=#FFF0F5'); NSC.Add('lawngreen=#7CFC00'); NSC.Add('lemonchiffon=#FFFACD'); NSC.Add('lightblue=#ADD8E6'); NSC.Add('lightcoral=#F08080'); NSC.Add('lightcyan=#E0FFFF'); NSC.Add('lightgoldenrodyellow=#FAFAD2'); NSC.Add('lightgreen=#90EE90'); NSC.Add('lightgrey=#D3D3D3'); NSC.Add('lightpink=#FFB6C1'); NSC.Add('lightsalmon=#FFA07A'); NSC.Add('lightseagreen=#20B2AA'); NSC.Add('lightskyblue=#87CEFA'); NSC.Add('lightslategray=#778899'); NSC.Add('lightsteelblue=#B0C4DE'); NSC.Add('lightyellow=#FFFFE0'); NSC.Add('lime=#00FF00'); NSC.Add('limegreen=#32CD32'); NSC.Add('linen=#FAF0E6'); NSC.Add('magenta=#FF00FF'); NSC.Add('maroon=#800000'); NSC.Add('mediumaquamarine=#66CDAA'); NSC.Add('mediumblue=#0000CD'); NSC.Add('mediumorchid=#BA55D3'); NSC.Add('mediumpurple=#9370DB'); NSC.Add('mediumseagreen=#3CB371'); NSC.Add('mediumslateblue=#7B68EE'); NSC.Add('mediumspringgreen=#00FA9A'); NSC.Add('mediumturquoise=#48D1CC'); NSC.Add('mediumvioletred=#C71585'); NSC.Add('midnightblue=#191970'); NSC.Add('mintcream=#F5FFFA'); NSC.Add('mistyrose=#FFE4E1'); NSC.Add('moccasin=#FFE4B5'); NSC.Add('navajowhite=#FFDEAD'); NSC.Add('navy=#000080'); NSC.Add('oldlace=#FDF5E6'); NSC.Add('olive=#808000'); NSC.Add('olivedrab=#6B8E23'); NSC.Add('orange=#FFA500'); NSC.Add('orangered=#FF4500'); NSC.Add('orchid=#DA70D6'); NSC.Add('palegoldenrod=#EEE8AA'); NSC.Add('palegreen=#98FB98'); NSC.Add('paleturquoise=#AFEEEE'); NSC.Add('palevioletred=#DB7093'); NSC.Add('papayawhip=#FFEFD5'); NSC.Add('peachpuff=#FFDAB9'); NSC.Add('peru=#CD853F'); NSC.Add('pink=#FFC0CB'); NSC.Add('plum=#DDA0DD'); NSC.Add('powderblue=#B0E0E6'); NSC.Add('purple=#800080'); NSC.Add('red=#FF0000'); NSC.Add('rosybrown=#BC8F8F'); NSC.Add('royalblue=#4169E1'); NSC.Add('saddlebrown=#8B4513'); NSC.Add('salmon=#FA8072'); NSC.Add('sandybrown=#F4A460'); NSC.Add('seagreen=#2E8B57'); NSC.Add('seashell=#FFF5EE'); NSC.Add('sienna=#A0522D'); NSC.Add('silver=#C0C0C0'); NSC.Add('skyblue=#87CEEB'); NSC.Add('slateblue=#6A5ACD'); NSC.Add('slategray=#708090'); NSC.Add('snow=#FFFAFA'); NSC.Add('springgreen=#00FF7F'); NSC.Add('steelblue=#4682B4'); NSC.Add('tan=#D2B48C'); NSC.Add('teal=#008080'); NSC.Add('thistle=#D8BFD8'); NSC.Add('tomato=#FF6347'); NSC.Add('turquoise=#40E0D0'); NSC.Add('violet=#EE82EE'); NSC.Add('wheat=#F5DEB3'); NSC.Add('white=#FFFFFF'); NSC.Add('whitesmoke=#F5F5F5'); NSC.Add('yellow=#FFFF00'); NSC.Add('yellowgreen=#9ACD32'); finalization NSC.Free; end.