/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot * * 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 1 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, see <http://www.gnu.org/licenses/>. */ using System; using System.Drawing; using System.IO; using Greenshot.Drawing.Fields; using GreenshotPlugin.Core; using System.Drawing.Drawing2D; using log4net; using System.Runtime.Serialization; using GreenshotPlugin.Effects; using GreenshotPlugin.Interfaces.Drawing; namespace Greenshot.Drawing { /// <summary> /// Description of BitmapContainer. /// </summary> [Serializable] public class ImageContainer : DrawableContainer, IImageContainer { private static readonly ILog Log = LogManager.GetLogger(typeof(ImageContainer)); private Image image; /// <summary> /// This is the shadow version of the bitmap, rendered once to save performance /// Do not serialize, as the shadow is recreated from the original bitmap if it's not available /// </summary> [NonSerialized] private Image _shadowBitmap; /// <summary> /// This is the offset for the shadow version of the bitmap /// Do not serialize, as the offset is recreated /// </summary> [NonSerialized] private Point _shadowOffset = new Point(-1, -1); public ImageContainer(Surface parent, string filename) : this(parent) { Load(filename); } public ImageContainer(Surface parent) : base(parent) { FieldChanged += BitmapContainer_OnFieldChanged; Init(); } protected override void OnDeserialized(StreamingContext streamingContext) { base.OnDeserialized(streamingContext); Init(); } private void Init() { CreateDefaultAdorners(); } protected override void InitializeFields() { AddField(GetType(), FieldType.SHADOW, false); } protected void BitmapContainer_OnFieldChanged(object sender, FieldChangedEventArgs e) { if (sender.Equals(this)) { if (FieldType.SHADOW.Equals(e.Field.FieldType)) { ChangeShadowField(); } } } public void ChangeShadowField() { bool shadow = GetFieldValueAsBool(FieldType.SHADOW); if (shadow) { CheckShadow(true); Width = _shadowBitmap.Width; Height = _shadowBitmap.Height; Left -= _shadowOffset.X; Top -= _shadowOffset.Y; } else { Width = image.Width; Height = image.Height; if (_shadowBitmap != null) { Left += _shadowOffset.X; Top += _shadowOffset.Y; } } } public Image Image { set { // Remove all current bitmaps DisposeImage(); DisposeShadow(); image = ImageHelper.Clone(value); bool shadow = GetFieldValueAsBool(FieldType.SHADOW); CheckShadow(shadow); if (!shadow) { Width = image.Width; Height = image.Height; } else { Width = _shadowBitmap.Width; Height = _shadowBitmap.Height; Left -= _shadowOffset.X; Top -= _shadowOffset.Y; } } get { return image; } } /// <summary> /// The bulk of the clean-up code is implemented in Dispose(bool) /// This Dispose is called from the Dispose and the Destructor. /// When disposing==true all non-managed resources should be freed too! /// </summary> /// <param name="disposing"></param> protected override void Dispose(bool disposing) { if (disposing) { DisposeImage(); DisposeShadow(); } base.Dispose(disposing); } private void DisposeImage() { image?.Dispose(); image = null; } private void DisposeShadow() { _shadowBitmap?.Dispose(); _shadowBitmap = null; } /// <summary> /// Make sure the content is also transformed. /// </summary> /// <param name="matrix"></param> public override void Transform(Matrix matrix) { int rotateAngle = CalculateAngle(matrix); // we currently assume only one transformation has been made. if (rotateAngle != 0) { Log.DebugFormat("Rotating element with {0} degrees.", rotateAngle); DisposeShadow(); using var tmpMatrix = new Matrix(); using (image) { image = ImageHelper.ApplyEffect(image, new RotateEffect(rotateAngle), tmpMatrix); } } base.Transform(matrix); } /// <summary> /// /// </summary> /// <param name="filename"></param> public void Load(string filename) { if (!File.Exists(filename)) { return; } // Always make sure ImageHelper.LoadBitmap results are disposed some time, // as we close the bitmap internally, we need to do it afterwards using (var tmpImage = ImageHelper.LoadImage(filename)) { Image = tmpImage; } Log.Debug("Loaded file: " + filename + " with resolution: " + Height + "," + Width); } /// <summary> /// This checks if a shadow is already generated /// </summary> /// <param name="shadow"></param> private void CheckShadow(bool shadow) { if (shadow && _shadowBitmap == null) { using var matrix = new Matrix(); _shadowBitmap = ImageHelper.ApplyEffect(image, new DropShadowEffect(), matrix); } } /// <summary> /// Draw the actual container to the graphics object /// </summary> /// <param name="graphics"></param> /// <param name="rm"></param> public override void Draw(Graphics graphics, RenderMode rm) { if (image != null) { bool shadow = GetFieldValueAsBool(FieldType.SHADOW); graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; if (shadow) { CheckShadow(true); graphics.DrawImage(_shadowBitmap, Bounds); } else { graphics.DrawImage(image, Bounds); } } } public override bool HasDefaultSize => true; public override Size DefaultSize => image.Size; } }