/* KeePass Password Safe - The Open-Source Password Manager Copyright (C) 2003-2020 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; #if !KeePassUAP using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; #endif namespace ModernKeePassLib.Utility { public static class GfxUtil { #if (!KeePassLibSD && !KeePassUAP) private sealed class GfxImage { public byte[] Data; public int Width; public int Height; public GfxImage(byte[] pbData, int w, int h) { this.Data = pbData; this.Width = w; this.Height = h; } #if DEBUG // For debugger display public override string ToString() { return (this.Width.ToString() + "x" + this.Height.ToString()); } #endif } #endif #if KeePassUAP public static Image LoadImage(byte[] pb) { if(pb == null) throw new ArgumentNullException("pb"); MemoryStream ms = new MemoryStream(pb, false); try { return Image.FromStream(ms); } finally { ms.Close(); } } #else public static Image LoadImage(byte[] pb) { if(pb == null) throw new ArgumentNullException("pb"); #if !KeePassLibSD // First try to load the data as ICO and afterwards as // normal image, because trying to load an ICO using // the normal image loading methods can result in a // low resolution image try { Image imgIco = ExtractBestImageFromIco(pb); if(imgIco != null) return imgIco; } catch(Exception) { Debug.Assert(false); } #endif MemoryStream ms = new MemoryStream(pb, false); try { return LoadImagePriv(ms); } finally { ms.Close(); } } private static Image LoadImagePriv(Stream s) { // Image.FromStream wants the stream to be open during // the whole lifetime of the image; as we can't guarantee // this, we make a copy of the image Image imgSrc = null; try { #if !KeePassLibSD imgSrc = Image.FromStream(s); Bitmap bmp = new Bitmap(imgSrc.Width, imgSrc.Height, PixelFormat.Format32bppArgb); try { bmp.SetResolution(imgSrc.HorizontalResolution, imgSrc.VerticalResolution); Debug.Assert(bmp.Size == imgSrc.Size); } catch(Exception) { Debug.Assert(false); } #else imgSrc = new Bitmap(s); Bitmap bmp = new Bitmap(imgSrc.Width, imgSrc.Height); #endif using(Graphics g = Graphics.FromImage(bmp)) { g.Clear(Color.Transparent); #if !KeePassLibSD g.DrawImageUnscaled(imgSrc, 0, 0); #else g.DrawImage(imgSrc, 0, 0); #endif } return bmp; } finally { if(imgSrc != null) imgSrc.Dispose(); } } #if !KeePassLibSD private static Image ExtractBestImageFromIco(byte[] pb) { List l = UnpackIco(pb); if((l == null) || (l.Count == 0)) return null; long qMax = 0; foreach(GfxImage gi in l) { if(gi.Width == 0) gi.Width = 256; if(gi.Height == 0) gi.Height = 256; qMax = Math.Max(qMax, (long)gi.Width * (long)gi.Height); } byte[] pbHdrPng = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; byte[] pbHdrJpeg = new byte[] { 0xFF, 0xD8, 0xFF }; Image imgBest = null; int bppBest = -1; foreach(GfxImage gi in l) { if(((long)gi.Width * (long)gi.Height) < qMax) continue; byte[] pbImg = gi.Data; Image img = null; try { if((pbImg.Length > pbHdrPng.Length) && MemUtil.ArraysEqual(pbHdrPng, MemUtil.Mid(pbImg, 0, pbHdrPng.Length))) img = GfxUtil.LoadImage(pbImg); else if((pbImg.Length > pbHdrJpeg.Length) && MemUtil.ArraysEqual(pbHdrJpeg, MemUtil.Mid(pbImg, 0, pbHdrJpeg.Length))) img = GfxUtil.LoadImage(pbImg); else { using(MemoryStream ms = new MemoryStream(pb, false)) { using(Icon ico = new Icon(ms, gi.Width, gi.Height)) { img = ico.ToBitmap(); } } } } catch(Exception) { Debug.Assert(false); } if(img == null) continue; if((img.Width < gi.Width) || (img.Height < gi.Height)) { Debug.Assert(false); img.Dispose(); continue; } int bpp = GetBitsPerPixel(img.PixelFormat); if(bpp > bppBest) { if(imgBest != null) imgBest.Dispose(); imgBest = img; bppBest = bpp; } else img.Dispose(); } return imgBest; } private static List UnpackIco(byte[] pb) { if(pb == null) { Debug.Assert(false); return null; } const int SizeICONDIR = 6; const int SizeICONDIRENTRY = 16; if(pb.Length < SizeICONDIR) return null; if(MemUtil.BytesToUInt16(pb, 0) != 0) return null; // Reserved, 0 if(MemUtil.BytesToUInt16(pb, 2) != 1) return null; // ICO type, 1 int n = MemUtil.BytesToUInt16(pb, 4); if(n < 0) { Debug.Assert(false); return null; } int cbDir = SizeICONDIR + (n * SizeICONDIRENTRY); if(pb.Length < cbDir) return null; List l = new List(); int iOffset = SizeICONDIR; for(int i = 0; i < n; ++i) { int w = pb[iOffset]; int h = pb[iOffset + 1]; if((w < 0) || (h < 0)) { Debug.Assert(false); return null; } int cb = MemUtil.BytesToInt32(pb, iOffset + 8); if(cb <= 0) return null; // Data must have header (even BMP) int p = MemUtil.BytesToInt32(pb, iOffset + 12); if(p < cbDir) return null; if((p + cb) > pb.Length) return null; try { byte[] pbImage = MemUtil.Mid(pb, p, cb); GfxImage img = new GfxImage(pbImage, w, h); l.Add(img); } catch(Exception) { Debug.Assert(false); return null; } iOffset += SizeICONDIRENTRY; } return l; } private static int GetBitsPerPixel(PixelFormat f) { int bpp = 0; switch(f) { case PixelFormat.Format1bppIndexed: bpp = 1; break; case PixelFormat.Format4bppIndexed: bpp = 4; break; case PixelFormat.Format8bppIndexed: bpp = 8; break; case PixelFormat.Format16bppArgb1555: case PixelFormat.Format16bppGrayScale: case PixelFormat.Format16bppRgb555: case PixelFormat.Format16bppRgb565: bpp = 16; break; case PixelFormat.Format24bppRgb: bpp = 24; break; case PixelFormat.Format32bppArgb: case PixelFormat.Format32bppPArgb: case PixelFormat.Format32bppRgb: bpp = 32; break; case PixelFormat.Format48bppRgb: bpp = 48; break; case PixelFormat.Format64bppArgb: case PixelFormat.Format64bppPArgb: bpp = 64; break; default: Debug.Assert(false); break; } return bpp; } public static Image ScaleImage(Image img, int w, int h) { return ScaleImage(img, w, h, ScaleTransformFlags.None); } /// /// Resize an image. /// /// Image to resize. /// Width of the returned image. /// Height of the returned image. /// Flags to customize scaling behavior. /// Resized image. This object is always different /// from (i.e. they can be /// disposed separately). public static Image ScaleImage(Image img, int w, int h, ScaleTransformFlags f) { if(img == null) throw new ArgumentNullException("img"); if(w < 0) throw new ArgumentOutOfRangeException("w"); if(h < 0) throw new ArgumentOutOfRangeException("h"); bool bUIIcon = ((f & ScaleTransformFlags.UIIcon) != ScaleTransformFlags.None); // We must return a Bitmap object for UIUtil.CreateScaledImage Bitmap bmp = new Bitmap(w, h, PixelFormat.Format32bppArgb); using(Graphics g = Graphics.FromImage(bmp)) { g.Clear(Color.Transparent); g.SmoothingMode = SmoothingMode.HighQuality; g.CompositingQuality = CompositingQuality.HighQuality; int wSrc = img.Width; int hSrc = img.Height; InterpolationMode im = InterpolationMode.HighQualityBicubic; if((wSrc > 0) && (hSrc > 0)) { if(bUIIcon && ((w % wSrc) == 0) && ((h % hSrc) == 0)) im = InterpolationMode.NearestNeighbor; // else if((w < wSrc) && (h < hSrc)) // im = InterpolationMode.HighQualityBilinear; } else { Debug.Assert(false); } g.InterpolationMode = im; RectangleF rSource = new RectangleF(0.0f, 0.0f, wSrc, hSrc); RectangleF rDest = new RectangleF(0.0f, 0.0f, w, h); AdjustScaleRects(ref rSource, ref rDest); g.DrawImage(img, rDest, rSource, GraphicsUnit.Pixel); } return bmp; } internal static void AdjustScaleRects(ref RectangleF rSource, ref RectangleF rDest) { // When enlarging images, apply a -0.5 offset to avoid // the scaled image being cropped on the top/left side; // when shrinking images, do not apply a -0.5 offset, // otherwise the image is cropped on the bottom/right // side; this applies to all interpolation modes if(rDest.Width > rSource.Width) rSource.X = rSource.X - 0.5f; if(rDest.Height > rSource.Height) rSource.Y = rSource.Y - 0.5f; // When shrinking, apply a +0.5 offset, such that the // scaled image is less cropped on the bottom/right side if(rDest.Width < rSource.Width) rSource.X = rSource.X + 0.5f; if(rDest.Height < rSource.Height) rSource.Y = rSource.Y + 0.5f; } #if DEBUG public static Image ScaleTest(Image[] vIcons) { Bitmap bmp = new Bitmap(1024, vIcons.Length * (256 + 12), PixelFormat.Format32bppArgb); using(Graphics g = Graphics.FromImage(bmp)) { g.Clear(Color.White); int[] v = new int[] { 16, 24, 32, 48, 64, 128, 256 }; int x; int y = 8; foreach(Image imgIcon in vIcons) { if(imgIcon == null) { Debug.Assert(false); continue; } x = 128; foreach(int q in v) { using(Image img = ScaleImage(imgIcon, q, q, ScaleTransformFlags.UIIcon)) { g.DrawImageUnscaled(img, x, y); } x += q + 8; } y += v[v.Length - 1] + 8; } } return bmp; } #endif // DEBUG #endif // !KeePassLibSD #endif // KeePassUAP internal static string ImageToDataUri(Image img) { if(img == null) { Debug.Assert(false); return string.Empty; } byte[] pb = null; using(MemoryStream ms = new MemoryStream()) { img.Save(ms, ImageFormat.Png); pb = ms.ToArray(); } return StrUtil.DataToDataUri(pb, "image/png"); } } }