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;
namespace PixiEditor.Models.ImageManipulation
{
public static class BitmapUtils
{
///
/// Converts pixel bytes to WriteableBitmap.
///
/// Width of bitmap.
/// Height of bitmap.
/// Bitmap byte array.
/// WriteableBitmap.
public static WriteableBitmap BytesToWriteableBitmap(int currentBitmapWidth, int currentBitmapHeight, byte[] byteArray)
{
WriteableBitmap bitmap = BitmapFactory.New(currentBitmapWidth, currentBitmapHeight);
if (byteArray != null)
{
bitmap.FromByteArray(byteArray);
}
return bitmap;
}
///
/// Converts layers bitmaps into one bitmap.
///
/// Width of final bitmap.
/// Height of final bitmap..
/// Layers to combine.
/// WriteableBitmap of layered bitmaps.
public static WriteableBitmap CombineLayers(int width, int height, IEnumerable layers, LayerStructure structure = null)
{
WriteableBitmap finalBitmap = BitmapFactory.New(width, height);
using (finalBitmap.GetBitmapContext())
{
for (int i = 0; i < layers.Count(); i++)
{
Layer layer = layers.ElementAt(i);
float layerOpacity = structure == null ? layer.Opacity : LayerStructureUtils.GetFinalLayerOpacity(layer, structure);
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 < layer.Width; x++)
{
Color previousColor = finalBitmap.GetPixel(x + layer.OffsetX, y + layer.OffsetY);
Color color = layer.GetPixel(x, y);
finalBitmap.SetPixel(x + layer.OffsetX, y + layer.OffsetY, BlendColor(previousColor, color, layerOpacity));
}
}
}
}
return finalBitmap;
}
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;
}
///
/// Generates simplified preview from Document, very fast, great for creating small previews. Creates uniform streched image.
///
/// Document which be used to generate preview.
/// Max width of preview.
/// Max height of preview.
/// WriteableBitmap image.
public static WriteableBitmap GeneratePreviewBitmap(Document document, int maxPreviewWidth, int maxPreviewHeight)
{
var opacityLayers = document.Layers.Where(x => x.IsVisible && x.Opacity > 0.8f);
return GeneratePreviewBitmap(
opacityLayers.Select(x => x.LayerBitmap),
opacityLayers.Select(x => x.OffsetX),
opacityLayers.Select(x => x.OffsetY),
document.Width,
document.Height,
maxPreviewWidth,
maxPreviewHeight);
}
public static WriteableBitmap GeneratePreviewBitmap(IEnumerable 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 => x.LayerBitmap),
opacityLayers.Select(x => x.OffsetX),
opacityLayers.Select(x => x.OffsetY),
width,
height,
maxPreviewWidth,
maxPreviewHeight);
}
public static WriteableBitmap GeneratePreviewBitmap(IEnumerable 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 GetPixelsForSelection(Layer[] layers, Coordinates[] selection)
{
Dictionary result = new();
foreach (Layer layer in layers)
{
Color[] pixels = new Color[selection.Length];
using (layer.LayerBitmap.GetBitmapContext())
{
for (int j = 0; j < pixels.Length; j++)
{
Coordinates position = layer.GetRelativePosition(selection[j]);
if (position.X < 0 || position.X > layer.Width - 1 || position.Y < 0 ||
position.Y > layer.Height - 1)
{
continue;
}
pixels[j] = layer.GetPixel(position.X, position.Y);
}
}
result[layer.LayerGuid] = pixels;
}
return result;
}
[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 layerBitmaps,
IEnumerable offsetsX,
IEnumerable 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);
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(offsetX, offsetY, bitmap.Width, bitmap.Height),
bitmap,
new Rect(0, 0, bitmap.Width, bitmap.Height));
}
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);
}
}
}