Files
modernkeepasslib/ModernKeePassLib/Utility/GfxUtil.cs

442 lines
11 KiB
C#
Raw Normal View History

2019-07-25 16:39:43 +02:00
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2020 Dominik Reichl <dominik.reichl@t-online.de>
2019-07-25 16:39:43 +02:00
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<GfxImage> 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<byte>(pbImg, 0, pbHdrPng.Length)))
img = GfxUtil.LoadImage(pbImg);
else if((pbImg.Length > pbHdrJpeg.Length) &&
MemUtil.ArraysEqual(pbHdrJpeg,
MemUtil.Mid<byte>(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<GfxImage> 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<GfxImage> l = new List<GfxImage>();
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<byte>(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);
}
/// <summary>
/// Resize an image.
/// </summary>
/// <param name="img">Image to resize.</param>
/// <param name="w">Width of the returned image.</param>
/// <param name="h">Height of the returned image.</param>
/// <param name="f">Flags to customize scaling behavior.</param>
/// <returns>Resized image. This object is always different
/// from <paramref name="img" /> (i.e. they can be
/// disposed separately).</returns>
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");
}
}
}