///////////////////////////////////////////////////////////////////////////////// // Paint.NET // // Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // // See src/Resources/Files/License.txt for full licensing and attribution // // details. // // . // ///////////////////////////////////////////////////////////////////////////////// using PaintDotNet.HistoryMementos; using PaintDotNet.SystemLayer; using PaintDotNet.Threading; using System; using System.Collections; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Threading; using System.Windows.Forms; namespace PaintDotNet.Actions { // TODO: split in to Action and Function public sealed class ResizeAction : DocumentWorkspaceAction { public static string StaticName { get { return PdnResources.GetString("ResizeAction.Name"); } } public static ImageResource StaticImage { get { return ImageResource.Get("Icons.MenuImageResizeIcon.png"); } } private sealed class FitSurfaceContext { private Surface dstSurface; private Surface srcSurface; private Rectangle[] dstRois; private ResamplingAlgorithm algorithm; public Surface DstSurface { get { return dstSurface; } } public Surface SrcSurface { get { return srcSurface; } } public Rectangle[] DstRois { get { return dstRois; } } public ResamplingAlgorithm Algorithm { get { return algorithm; } } public event Procedure RenderedRect; private void OnRenderedRect() { if (RenderedRect != null) { RenderedRect(); } } public void FitSurface(object context) { int index = (int)context; dstSurface.FitSurface(algorithm, srcSurface, dstRois[index]); OnRenderedRect(); } public FitSurfaceContext(Surface dstSurface, Surface srcSurface, Rectangle[] dstRois, ResamplingAlgorithm algorithm) { this.dstSurface = dstSurface; this.srcSurface = srcSurface; this.dstRois = dstRois; this.algorithm = algorithm; } } private static BitmapLayer ResizeLayer(BitmapLayer layer, int width, int height, ResamplingAlgorithm algorithm, int tileCount, Procedure progressCallback, ref bool pleaseStopMonitor) { Surface surface = new Surface(width, height); surface.Clear(ColorBgra.FromBgra(255, 255, 255, 0)); PaintDotNet.Threading.ThreadPool threadPool = new PaintDotNet.Threading.ThreadPool(); int rectCount; if (tileCount == 0) { rectCount = Processor.LogicalCpuCount; } else { rectCount = tileCount; } Rectangle[] rects = new Rectangle[rectCount]; Utility.SplitRectangle(surface.Bounds, rects); FitSurfaceContext fsc = new FitSurfaceContext(surface, layer.Surface, rects, algorithm); if (progressCallback != null) { fsc.RenderedRect += progressCallback; } WaitCallback callback = new WaitCallback(fsc.FitSurface); for (int i = 0; i < rects.Length; ++i) { if (pleaseStopMonitor) { break; } else { threadPool.QueueUserWorkItem(callback, BoxedConstants.GetInt32(i)); } } threadPool.Drain(); threadPool.DrainExceptions(); if (pleaseStopMonitor) { surface.Dispose(); surface = null; } BitmapLayer newLayer; if (surface == null) { newLayer = null; } else { newLayer = new BitmapLayer(surface, true); newLayer.LoadProperties(layer.SaveProperties()); } if (progressCallback != null) { fsc.RenderedRect -= progressCallback; } return newLayer; } public static BitmapLayer ResizeLayer(BitmapLayer layer, int width, int height, ResamplingAlgorithm algorithm) { bool pleaseStop = false; return ResizeLayer(layer, width, height, algorithm, 0, null, ref pleaseStop); } private class ResizeProgressDialog : CallbackWithProgressDialog { private int maxTiles; private int tilesCompleted = 0; private int tilesPerLayer; private Document dst; private Document src; private Size newSize; private ResamplingAlgorithm algorithm; private bool returnVal; private bool pleaseStop = false; public ResizeProgressDialog(Control owner, Document dst, Document src, Size newSize, ResamplingAlgorithm algorithm) : base (owner, PdnInfo.GetBareProductName(), PdnResources.GetString("ResizeAction.ProgressDialog.Description")) { this.dst = dst; this.src = src; this.newSize = newSize; this.algorithm = algorithm; this.tilesPerLayer = 50 * Processor.LogicalCpuCount; this.maxTiles = tilesPerLayer * src.Layers.Count; this.Icon = Utility.ImageToIcon(StaticImage.Reference); } protected override void OnCancelClick() { this.pleaseStop = true; base.OnCancelClick(); } private void RenderedRectHandler() { this.Owner.BeginInvoke(new Procedure(MarshaledProgressUpdate)); } private void MarshaledProgressUpdate() { ++tilesCompleted; double progress = 100.0 * ((double)tilesCompleted / (double)maxTiles); this.Progress = (int)Math.Round(progress); } public bool DoResize() { DialogResult result = this.ShowDialog(true, false, new ThreadStart(ResizeDocument)); if (!this.returnVal && !this.Cancelled) { Utility.ErrorBox(this.Owner, PdnResources.GetString("ResizeAction.PerformAction.UnspecifiedError")); } return this.returnVal; } private void ResizeDocument() { this.pleaseStop = false; // This is only sort of a hack: we must try and allocate enough for 2 extra layer-sized buffers // Then we free them immediately. This is just so that if we don't have enough memory that we'll // fail sooner rather than later. Surface s1 = new Surface(this.newSize); Surface s2 = new Surface(this.newSize); try { foreach (Layer layer in src.Layers) { if (this.pleaseStop) { this.returnVal = false; return; } if (layer is BitmapLayer) { Layer newLayer = ResizeLayer((BitmapLayer)layer, this.newSize.Width, this.newSize.Height, this.algorithm, this.tilesPerLayer, new Procedure(RenderedRectHandler), ref this.pleaseStop); if (newLayer == null) { this.returnVal = false; return; } dst.Layers.Add(newLayer); } else { throw new InvalidOperationException("Resize does not support Layers that are not BitmapLayers"); } } } finally { s1.Dispose(); s2.Dispose(); } this.returnVal = true; } } public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) { int newWidth; int newHeight; double newDpu; MeasurementUnit newDpuUnit; string resamplingAlgorithm = Settings.CurrentUser.GetString(PdnSettings.LastResamplingMethod, ResamplingAlgorithm.SuperSampling.ToString()); ResamplingAlgorithm alg; try { alg = (ResamplingAlgorithm)Enum.Parse(typeof(ResamplingAlgorithm), resamplingAlgorithm, true); } catch { alg = ResamplingAlgorithm.SuperSampling; } bool maintainAspect = Settings.CurrentUser.GetBoolean(PdnSettings.LastMaintainAspectRatio, true); using (ResizeDialog rd = new ResizeDialog()) { rd.OriginalSize = documentWorkspace.Document.Size; rd.OriginalDpuUnit = documentWorkspace.Document.DpuUnit; rd.OriginalDpu = documentWorkspace.Document.DpuX; rd.ImageHeight = documentWorkspace.Document.Height; rd.ImageWidth = documentWorkspace.Document.Width; rd.ResamplingAlgorithm = alg; rd.LayerCount = documentWorkspace.Document.Layers.Count; rd.Units = rd.OriginalDpuUnit; rd.Resolution = documentWorkspace.Document.DpuX; rd.Units = PdnSettings.GetLastNonPixelUnits(); rd.ConstrainToAspect = maintainAspect; DialogResult result = rd.ShowDialog(documentWorkspace); if (result == DialogResult.Cancel) { return null; } Settings.CurrentUser.SetString(PdnSettings.LastResamplingMethod, rd.ResamplingAlgorithm.ToString()); Settings.CurrentUser.SetBoolean(PdnSettings.LastMaintainAspectRatio, rd.ConstrainToAspect); newDpuUnit = rd.Units; newWidth = rd.ImageWidth; newHeight = rd.ImageHeight; newDpu = rd.Resolution; alg = rd.ResamplingAlgorithm; if (newDpuUnit != MeasurementUnit.Pixel) { Settings.CurrentUser.SetString(PdnSettings.LastNonPixelUnits, newDpuUnit.ToString()); if (documentWorkspace.AppWorkspace.Units != MeasurementUnit.Pixel) { documentWorkspace.AppWorkspace.Units = newDpuUnit; } } // if the new size equals the old size, there's really no point in doing anything if (documentWorkspace.Document.Size == new Size(rd.ImageWidth, rd.ImageHeight) && documentWorkspace.Document.DpuX == newDpu && documentWorkspace.Document.DpuUnit == newDpuUnit) { return null; } } HistoryMemento ha; if (newWidth == documentWorkspace.Document.Width && newHeight == documentWorkspace.Document.Height) { // Only adjusting Dpu or DpuUnit ha = new MetaDataHistoryMemento(StaticName, StaticImage, documentWorkspace); documentWorkspace.Document.DpuUnit = newDpuUnit; documentWorkspace.Document.DpuX = newDpu; documentWorkspace.Document.DpuY = newDpu; } else { try { using (new WaitCursorChanger(documentWorkspace)) { ha = new ReplaceDocumentHistoryMemento(StaticName, StaticImage, documentWorkspace); } Document newDocument = new Document(newWidth, newHeight); newDocument.ReplaceMetaDataFrom(documentWorkspace.Document); newDocument.DpuUnit = newDpuUnit; newDocument.DpuX = newDpu; newDocument.DpuY = newDpu; ResizeProgressDialog rpd = new ResizeProgressDialog(documentWorkspace, newDocument, documentWorkspace.Document, new Size(newWidth, newHeight), alg); Utility.GCFullCollect(); bool result = rpd.DoResize(); if (!result) { return null; } documentWorkspace.Document = newDocument; } catch (WorkerThreadException ex) { if (ex.InnerException is OutOfMemoryException) { Utility.ErrorBox(documentWorkspace, PdnResources.GetString("ResizeAction.PerformAction.OutOfMemory")); return null; } else { throw; } } catch (OutOfMemoryException) { Utility.ErrorBox(documentWorkspace, PdnResources.GetString("ResizeAction.PerformAction.OutOfMemory")); return null; } } return ha; } public ResizeAction() : base(ActionFlags.KeepToolActive) { // We use ActionFlags.KeepToolActive because opening this dialog does not necessitate // refreshing the tool. This is handled by DocumentWorkspace.set_Document as appropriate. } } }