|
@@ -1,12 +1,15 @@
|
|
|
-using System;
|
|
|
+using PixiEditor.Models.DataHolders;
|
|
|
+using PixiEditor.Models.Layers;
|
|
|
+using PixiEditor.Models.Layers.Utils;
|
|
|
+using PixiEditor.Models.Position;
|
|
|
+using PixiEditor.Parser;
|
|
|
+using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
+using System.Runtime.CompilerServices;
|
|
|
using System.Windows;
|
|
|
using System.Windows.Media;
|
|
|
using System.Windows.Media.Imaging;
|
|
|
-using PixiEditor.Models.DataHolders;
|
|
|
-using PixiEditor.Models.Layers;
|
|
|
-using PixiEditor.Models.Position;
|
|
|
|
|
|
namespace PixiEditor.Models.ImageManipulation
|
|
|
{
|
|
@@ -37,7 +40,7 @@ namespace PixiEditor.Models.ImageManipulation
|
|
|
/// <param name="height">Height of final bitmap.</param>.
|
|
|
/// <param name="layers">Layers to combine.</param>
|
|
|
/// <returns>WriteableBitmap of layered bitmaps.</returns>
|
|
|
- public static WriteableBitmap CombineLayers(int width, int height, params Layer[] layers)
|
|
|
+ public static WriteableBitmap CombineLayers(int width, int height, Layer[] layers, LayerStructure structure = null)
|
|
|
{
|
|
|
WriteableBitmap finalBitmap = BitmapFactory.New(width, height);
|
|
|
|
|
@@ -45,33 +48,24 @@ namespace PixiEditor.Models.ImageManipulation
|
|
|
{
|
|
|
for (int i = 0; i < layers.Length; i++)
|
|
|
{
|
|
|
- float layerOpacity = layers[i].Opacity;
|
|
|
+ float layerOpacity = structure == null ? layers[i].Opacity : LayerStructureUtils.GetFinalLayerOpacity(layers[i], structure);
|
|
|
Layer layer = layers[i];
|
|
|
|
|
|
- for (int y = 0; y < layers[i].Height; y++)
|
|
|
+ if (layer.OffsetX < 0 || layer.OffsetY < 0 ||
|
|
|
+ layer.Width + layer.OffsetX > layer.MaxWidth ||
|
|
|
+ layer.Height + layer.OffsetY > layer.MaxHeight)
|
|
|
+ {
|
|
|
+ throw new InvalidOperationException("Layers must not extend beyond canvas borders");
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int y = 0; y < layer.Height; y++)
|
|
|
{
|
|
|
- for (int x = 0; x < layers[i].Width; x++)
|
|
|
+ for (int x = 0; x < layer.Width; x++)
|
|
|
{
|
|
|
+ Color previousColor = finalBitmap.GetPixel(x + layer.OffsetX, y + layer.OffsetY);
|
|
|
Color color = layer.GetPixel(x, y);
|
|
|
- if (i > 0 && ((color.A < 255 && color.A > 0) || (layerOpacity < 1f && layerOpacity > 0 && color.A > 0)))
|
|
|
- {
|
|
|
- var lastLayerPixel = finalBitmap.GetPixel(x + layer.OffsetX, y + layer.OffsetY);
|
|
|
- byte pixelA = (byte)(color.A * layerOpacity);
|
|
|
- byte r = (byte)((color.R * pixelA / 255) + (lastLayerPixel.R * lastLayerPixel.A * (255 - pixelA) / (255 * 255)));
|
|
|
- byte g = (byte)((color.G * pixelA / 255) + (lastLayerPixel.G * lastLayerPixel.A * (255 - pixelA) / (255 * 255)));
|
|
|
- byte b = (byte)((color.B * pixelA / 255) + (lastLayerPixel.B * lastLayerPixel.A * (255 - pixelA) / (255 * 255)));
|
|
|
- byte a = (byte)(pixelA + (lastLayerPixel.A * (255 - pixelA) / 255));
|
|
|
- color = Color.FromArgb(a, r, g, b);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- color = Color.FromArgb(color.A, color.R, color.G, color.B);
|
|
|
- }
|
|
|
-
|
|
|
- if (color.A > 0)
|
|
|
- {
|
|
|
- finalBitmap.SetPixel(x + layer.OffsetX, y + layer.OffsetY, color);
|
|
|
- }
|
|
|
+
|
|
|
+ finalBitmap.SetPixel(x + layer.OffsetX, y + layer.OffsetY, BlendColor(previousColor, color, layerOpacity));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -80,37 +74,64 @@ namespace PixiEditor.Models.ImageManipulation
|
|
|
return finalBitmap;
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
+ public static Color GetColorAtPointCombined(int x, int y, params Layer[] layers)
|
|
|
+ {
|
|
|
+ Color prevColor = Color.FromArgb(0, 0, 0, 0);
|
|
|
+
|
|
|
+ for (int i = 0; i < layers.Length; i++)
|
|
|
+ {
|
|
|
+ Color color = layers[i].GetPixelWithOffset(x, y);
|
|
|
+ float layerOpacity = layers[i].Opacity;
|
|
|
+
|
|
|
+ prevColor = BlendColor(prevColor, color, layerOpacity);
|
|
|
+ }
|
|
|
+
|
|
|
+ return prevColor;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
/// Generates simplified preview from Document, very fast, great for creating small previews. Creates uniform streched image.
|
|
|
/// </summary>
|
|
|
- /// <param name="document">Document which will be used to generate preview.</param>
|
|
|
+ /// <param name="document">Document which be used to generate preview.</param>
|
|
|
/// <param name="maxPreviewWidth">Max width of preview.</param>
|
|
|
/// <param name="maxPreviewHeight">Max height of preview.</param>
|
|
|
/// <returns>WriteableBitmap image.</returns>
|
|
|
public static WriteableBitmap GeneratePreviewBitmap(Document document, int maxPreviewWidth, int maxPreviewHeight)
|
|
|
{
|
|
|
- return GeneratePreviewBitmap(document.Layers, maxPreviewWidth, maxPreviewHeight, document.Width, document.Height, 0, 0);
|
|
|
+ var opacityLayers = document.Layers.Where(x => x.IsVisible && x.Opacity > 0.8f);
|
|
|
+
|
|
|
+ return GeneratePreviewBitmap(opacityLayers, document.Width, document.Height, maxPreviewWidth, maxPreviewHeight);
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Generates simplified preview from layers, very fast, great for creating small previews. Creates uniform streched image.
|
|
|
- /// </summary>
|
|
|
- /// <param name="layers">Layers which will be used to generate preview.</param>
|
|
|
- /// <param name="maxPreviewWidth">Max width of preview.</param>
|
|
|
- /// <param name="maxPreviewHeight">Max height of preview.</param>
|
|
|
- /// <returns>WriteableBitmap image.</returns>
|
|
|
- public static WriteableBitmap GeneratePreviewBitmap(IEnumerable<Layer> layers, int maxPreviewWidth, int maxPreviewHeight, bool showHidden = false)
|
|
|
+ public static WriteableBitmap GeneratePreviewBitmap(IEnumerable<Layer> layers, int width, int height, int maxPreviewWidth, int maxPreviewHeight)
|
|
|
{
|
|
|
- int minOffsetX = layers.Min(x => x.OffsetX);
|
|
|
- int minOffsetY = layers.Min(x => x.OffsetY);
|
|
|
- int width = layers.Max(x => x.OffsetX + x.Width) - minOffsetX;
|
|
|
- int height = layers.Max(x => x.OffsetY + x.Height) - minOffsetY;
|
|
|
- return GeneratePreviewBitmap(layers, maxPreviewWidth, maxPreviewHeight, width, height, minOffsetX, minOffsetY, showHidden);
|
|
|
+ return GeneratePreviewBitmap(
|
|
|
+ layers.Select(x => x.LayerBitmap),
|
|
|
+ layers.Select(x => x.OffsetX),
|
|
|
+ layers.Select(x => x.OffsetY),
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ maxPreviewWidth,
|
|
|
+ maxPreviewHeight);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static WriteableBitmap GeneratePreviewBitmap(IEnumerable<SerializableLayer> layers, int width, int height, int maxPreviewWidth, int maxPreviewHeight)
|
|
|
+ {
|
|
|
+ var opacityLayers = layers.Where(x => x.IsVisible && x.Opacity > 0.8f);
|
|
|
+
|
|
|
+ return GeneratePreviewBitmap(
|
|
|
+ opacityLayers.Select(x => BytesToWriteableBitmap(x.Width, x.Height, x.BitmapBytes)),
|
|
|
+ opacityLayers.Select(x => x.OffsetX),
|
|
|
+ opacityLayers.Select(x => x.OffsetY),
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ maxPreviewWidth,
|
|
|
+ maxPreviewHeight);
|
|
|
}
|
|
|
|
|
|
public static Dictionary<Guid, Color[]> GetPixelsForSelection(Layer[] layers, Coordinates[] selection)
|
|
|
{
|
|
|
- Dictionary<Guid, Color[]> result = new ();
|
|
|
+ Dictionary<Guid, Color[]> result = new();
|
|
|
|
|
|
foreach (Layer layer in layers)
|
|
|
{
|
|
@@ -137,23 +158,71 @@ namespace PixiEditor.Models.ImageManipulation
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
- private static WriteableBitmap GeneratePreviewBitmap(IEnumerable<Layer> layers, int maxPreviewWidth, int maxPreviewHeight, int width, int height, int minOffsetX, int minOffsetY, bool showHidden = false)
|
|
|
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
+ private static Color BlendColor(Color previousColor, Color color, float opacity)
|
|
|
+ {
|
|
|
+ if ((color.A < 255 && color.A > 0) || (opacity < 1f && opacity > 0 && color.A > 0))
|
|
|
+ {
|
|
|
+ byte pixelA = (byte)(color.A * opacity);
|
|
|
+ byte r = (byte)((color.R * pixelA / 255) + (previousColor.R * previousColor.A * (255 - pixelA) / (255 * 255)));
|
|
|
+ byte g = (byte)((color.G * pixelA / 255) + (previousColor.G * previousColor.A * (255 - pixelA) / (255 * 255)));
|
|
|
+ byte b = (byte)((color.B * pixelA / 255) + (previousColor.B * previousColor.A * (255 - pixelA) / (255 * 255)));
|
|
|
+ byte a = (byte)(pixelA + (previousColor.A * (255 - pixelA) / 255));
|
|
|
+ color = Color.FromArgb(a, r, g, b);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ color = Color.FromArgb(color.A, color.R, color.G, color.B);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (color.A > 0)
|
|
|
+ {
|
|
|
+ return color;
|
|
|
+ }
|
|
|
+
|
|
|
+ return previousColor;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static WriteableBitmap GeneratePreviewBitmap(
|
|
|
+ IEnumerable<WriteableBitmap> layerBitmaps,
|
|
|
+ IEnumerable<int> offsetsX,
|
|
|
+ IEnumerable<int> offsetsY,
|
|
|
+ int width,
|
|
|
+ int height,
|
|
|
+ int maxPreviewWidth,
|
|
|
+ int maxPreviewHeight)
|
|
|
{
|
|
|
+ int count = layerBitmaps.Count();
|
|
|
+
|
|
|
+ if (count != offsetsX.Count() || count != offsetsY.Count())
|
|
|
+ {
|
|
|
+ throw new ArgumentException("There were not the same amount of bitmaps and offsets", nameof(layerBitmaps));
|
|
|
+ }
|
|
|
+
|
|
|
WriteableBitmap previewBitmap = BitmapFactory.New(width, height);
|
|
|
|
|
|
- // 0.8 because blit doesn't take into consideration layer opacity. Small images with opacity > 80% are simillar enough.
|
|
|
- foreach (var layer in layers.Where(x => (x.IsVisible || showHidden) && x.Opacity > 0.8f))
|
|
|
+ var layerBitmapsEnumerator = layerBitmaps.GetEnumerator();
|
|
|
+ var offsetsXEnumerator = offsetsX.GetEnumerator();
|
|
|
+ var offsetsYEnumerator = offsetsY.GetEnumerator();
|
|
|
+
|
|
|
+ while (layerBitmapsEnumerator.MoveNext())
|
|
|
{
|
|
|
+ offsetsXEnumerator.MoveNext();
|
|
|
+ offsetsYEnumerator.MoveNext();
|
|
|
+
|
|
|
+ var bitmap = layerBitmapsEnumerator.Current;
|
|
|
+ var offsetX = offsetsXEnumerator.Current;
|
|
|
+ var offsetY = offsetsYEnumerator.Current;
|
|
|
+
|
|
|
previewBitmap.Blit(
|
|
|
- new Rect(layer.OffsetX - minOffsetX, layer.OffsetY - minOffsetY, layer.Width, layer.Height),
|
|
|
- layer.LayerBitmap,
|
|
|
- new Rect(0, 0, layer.Width, layer.Height));
|
|
|
+ new Rect(offsetX, offsetY, bitmap.Width, bitmap.Height),
|
|
|
+ bitmap,
|
|
|
+ new Rect(0, 0, bitmap.Width, bitmap.Height));
|
|
|
}
|
|
|
|
|
|
- int finalWidth = width >= height ? maxPreviewWidth : (int)Math.Ceiling(width / ((float)height / maxPreviewHeight));
|
|
|
- int finalHeight = height > width ? maxPreviewHeight : (int)Math.Ceiling(height / ((float)width / maxPreviewWidth));
|
|
|
-
|
|
|
- return previewBitmap.Resize(width, height, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
|
|
|
+ int newWidth = width >= height ? maxPreviewWidth : (int)Math.Ceiling(width / ((float)height / maxPreviewHeight));
|
|
|
+ int newHeight = height > width ? maxPreviewHeight : (int)Math.Ceiling(height / ((float)width / maxPreviewWidth));
|
|
|
+ return previewBitmap.Resize(newWidth, newHeight, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
|
|
|
}
|
|
|
}
|
|
|
}
|