Skip to content

Layer Opacity #81

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions Pixed/Controls/LayerListControl.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,25 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Pixed.Controls"
xmlns:models="clr-namespace:Pixed.Models"
xmlns:models="clr-namespace:Pixed.Models"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:DataType="models:Layer">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Gray" BorderThickness="1">
<Image Width="32" Height="32" Source="{Binding RenderSource}" RenderOptions.BitmapInterpolationMode="None"/>
</Border>
<TextBlock Text="{Binding Name}" Foreground="White" VerticalAlignment="Center" Margin="4"/>
</StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="34"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="34"/>
</Grid.ColumnDefinitions>
<Border BorderBrush="Gray" BorderThickness="1">
<Image Width="32" Height="32" Source="{Binding RenderSource}" RenderOptions.BitmapInterpolationMode="None"/>
</Border>
<TextBlock Grid.Column="1" Text="{Binding Name}" Foreground="White" VerticalAlignment="Center" Margin="4"/>
<Button Grid.Column="2" Command="{Binding ChangeOpacityCommand}">
<ToolTip.Tip>
<local:SimpleTooltip Title="Change layer opacity"/>
</ToolTip.Tip>
<Image Source="avares://Pixed.Core/Resources/Icons/eye.png"/>
</Button>
</Grid>
</UserControl>
11 changes: 6 additions & 5 deletions Pixed/IO/PiskelProjectSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using BigGustave;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Linq;
using Pixed.Models;
using System;
using System.Collections.ObjectModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -41,7 +41,7 @@ public PixedModel Deserialize(Stream stream, ApplicationData applicationData)
string str = match.Value[7..];
byte[] data = Convert.FromBase64String(str);
MemoryStream memoryStream = new(data);
var png = Png.Open(memoryStream);
Bitmap bitmap = (Bitmap)Image.FromStream(memoryStream);

for (int f = 0; f < frames.Count; f++)
{
Expand All @@ -51,8 +51,8 @@ public PixedModel Deserialize(Stream stream, ApplicationData applicationData)
{
for (int y = 0; y < height; y++)
{
var pixel = png.GetPixel(x + (f * width), y);
frameLayerData[y * width + x] = new UniColor(pixel.A, pixel.R, pixel.G, pixel.B);
UniColor pixel = bitmap.GetPixel(x + (f * width), y);
frameLayerData[y * width + x] = pixel;
}
}

Expand All @@ -61,6 +61,7 @@ public PixedModel Deserialize(Stream stream, ApplicationData applicationData)
}

memoryStream.Dispose();
bitmap.Dispose();
}
}

Expand Down
46 changes: 15 additions & 31 deletions Pixed/IO/PngProjectSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using BigGustave;
using Pixed.Models;
using Pixed.Models;
using Pixed.Utils;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace Pixed.IO;
Expand All @@ -9,23 +11,12 @@ internal class PngProjectSerializer : IPixedProjectSerializer
public int ColumnsCount { get; set; } = 1;
public PixedModel Deserialize(Stream stream, ApplicationData applicationData)
{
Png image = Png.Open(stream);
int width = image.Width;
int height = image.Height;
int[] pixels = new int[width * height];
Bitmap bitmap = (Bitmap)Image.FromStream(stream);
var colors = bitmap.ToPixelColors();

for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
var pixel = image.GetPixel(x, y);
UniColor color = new(pixel.A, pixel.R, pixel.G, pixel.B);
pixels[y * width + x] = color;
}
}

Layer layer = Layer.FromColors(pixels, width, height, "Layer 0");
Layer layer = Layer.FromColors(colors, bitmap.Width, bitmap.Height, "Layer 0");
Frame frame = Frame.FromLayers([layer]);
bitmap.Dispose();
return PixedModel.FromFrames([frame], applicationData.GenerateName(), applicationData);
}

Expand All @@ -36,28 +27,19 @@ public void Serialize(Stream stream, PixedModel model, bool close)
int rows = (int)Math.Ceiling((double)model.Frames.Count / (double)ColumnsCount);
int width = model.Width * ColumnsCount;
int height = model.Height * rows;
var builder = PngBuilder.Create(width, height, true);
Bitmap outputBitmap = new Bitmap(width, height);
Graphics graphics = Graphics.FromImage(outputBitmap);

int frameColumn = 0;
int frameRow = 0;
for (int a = 0; a < model.Frames.Count; a++)
{
//TODO optimize
Frame frame = model.Frames[a];
Layer layer = Layer.MergeAll([.. frame.Layers]);
var pixels = layer.GetPixels();
var frameBitmap = frame.Render();
int x1 = frameColumn * model.Width;
int y1 = frameRow * model.Height;
for (int x = 0; x < model.Width; x++)
{
for (int y = 0; y < model.Height; y++)
{
UniColor color = pixels[y * model.Width + x];
var pixel = new BigGustave.Pixel(color.R, color.G, color.B, color.A, false);
builder.SetPixel(pixel, x1 + x, y1 + y);
}
}

graphics.DrawImage(frameBitmap, new Point(x1, y1));
frameColumn++;

if (frameColumn == ColumnsCount)
Expand All @@ -67,7 +49,9 @@ public void Serialize(Stream stream, PixedModel model, bool close)
}
}

builder.Save(stream);
graphics.Dispose();
outputBitmap.Save(stream, ImageFormat.Png);
outputBitmap.Dispose();

if (close)
{
Expand Down
28 changes: 4 additions & 24 deletions Pixed/Models/Frame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,38 +122,18 @@ public void RefreshLayerRenderSources()
}
}

public void RefreshRenderSource()
public void RefreshRenderSource(List<Pixel>? pixels = null)
{
RenderSource = Render().ToAvaloniaBitmap();
RenderSource = Render(pixels).ToAvaloniaBitmap();
}

public System.Drawing.Bitmap RenderTransparent(List<Pixel>? pixels = null)
public System.Drawing.Bitmap Render(List<Pixel>? pixels = null)
{
System.Drawing.Bitmap render = new(Width, Height);
Graphics g = Graphics.FromImage(render);
for (int a = 0; a < _layers.Count; a++)
{
if (a == SelectedLayer)
{
continue;
}

g.DrawImage(_layers[a].Render().OpacityImage(0.5f), 0, 0);
}

var rendered = CurrentLayer.Render(pixels);
g.DrawImage(rendered, 0, 0);

return render;
}

public System.Drawing.Bitmap Render()
{
System.Drawing.Bitmap render = new(Width, Height);
Graphics g = Graphics.FromImage(render);
for (int a = 0; a < _layers.Count; a++)
{
g.DrawImage(_layers[a].Render(), 0, 0);
g.DrawImage(_layers[a].Render(a == SelectedLayer ? pixels : null), 0, 0);
}

return render;
Expand Down
79 changes: 45 additions & 34 deletions Pixed/Models/Layer.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using Pixed.IO;
using Pixed.Utils;
using Pixed.Windows;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Input;
using Bitmap = Avalonia.Media.Imaging.Bitmap;

namespace Pixed.Models;
Expand All @@ -18,12 +21,12 @@ internal class Layer : PropertyChangedBase, IPixedSerializer
private int _height;
private System.Drawing.Bitmap _renderedBitmap = null;
private bool _needRerender = true;
private float _opacity = 1.0f;
private double _opacity = 100.0d;
private string _name = string.Empty;
private Bitmap? _renderSource = null;
private readonly string _id;

public float Opacity
public double Opacity
{
get => _opacity;
set
Expand Down Expand Up @@ -56,8 +59,10 @@ public Bitmap RenderSource
public string Id => _id;
public int Width => _width;
public int Height => _height;
public ICommand? ChangeOpacityCommand { get; }
public Layer(int width, int height)
{
ChangeOpacityCommand = new AsyncCommand(ChangeOpacityAction);
_id = Guid.NewGuid().ToString();
_width = width;
_height = height;
Expand All @@ -68,6 +73,7 @@ public Layer(int width, int height)

private Layer(int width, int height, int[] pixels)
{
ChangeOpacityCommand = new AsyncCommand(ChangeOpacityAction);
_id = Guid.NewGuid().ToString();
_width = width;
_height = height;
Expand Down Expand Up @@ -107,15 +113,13 @@ public void SetPixels(List<Pixel> pixels)

public void MergeLayers(Layer layer2)
{
int transparent = UniColor.Transparent;
System.Drawing.Bitmap outputBitmap = new(Width, Height);
Graphics graphics = Graphics.FromImage(outputBitmap);
graphics.DrawImage(Render(), new Point(0, 0));
graphics.DrawImage(layer2.Render(), new Point(0, 0));
graphics.Dispose();

for (int a = 0; a < _pixels.Length; a++)
{
if (layer2._pixels[a] != transparent && _pixels[a] != layer2._pixels[a])
{
_pixels[a] = layer2._pixels[a];
}
}
_pixels = outputBitmap.ToPixelColors();

_needRerender = true;
}
Expand Down Expand Up @@ -151,20 +155,19 @@ public System.Drawing.Bitmap Render(List<Pixel>? modifiedPixels = null)
int[] pixels = new int[_width * _height];
_pixels.CopyTo(pixels, 0);

for (int i = 0; i < pixels.Length; i++)
foreach (var pixel in modifiedPixels ?? [])
{
UniColor color = pixels[i];

if (color.A * _opacity > 0)
{
int newColor = UniColor.WithAlpha((byte)(255 * _opacity), color);
pixels[i] = newColor;
}
pixels[pixel.Y * _width + pixel.X] = pixel.Color;
}

foreach (var pixel in modifiedPixels ?? [])
if(Opacity != 100)
{
pixels[pixel.Y * _width + pixel.X] = pixel.Color;
for(int a = 0; a < pixels.Length; a++)
{
UniColor color = pixels[a];
color.A = (byte)((Opacity / 100d) * color.A);
pixels[a] = color;
}
}

System.Drawing.Bitmap bitmap = new(_width, _height);
Expand All @@ -183,7 +186,7 @@ public bool ContainsPixel(int x, int y)

public void Serialize(Stream stream)
{
stream.WriteFloat(Opacity);
stream.WriteDouble(Opacity);
stream.WriteInt(Width);
stream.WriteInt(Height);
stream.WriteString(Name);
Expand All @@ -193,7 +196,7 @@ public void Serialize(Stream stream)

public void Deserialize(Stream stream)
{
Opacity = stream.ReadFloat();
Opacity = stream.ReadDouble();
_width = stream.ReadInt();
_height = stream.ReadInt();
_name = stream.ReadString();
Expand All @@ -216,18 +219,6 @@ public static Layer FromColors(int[] colors, int width, int height, string name)
return layer;
}

public static Layer MergeAll(Layer[] layers)
{
Layer first = layers.First().Clone();

for (int a = 1; a < layers.Length; a++)
{
first.MergeLayers(layers[a]);
}

return first;
}

private void SetPixelPrivate(int x, int y, int color)
{
if (x < 0 || y < 0 || x >= _width || y >= _height)
Expand All @@ -237,4 +228,24 @@ private void SetPixelPrivate(int x, int y, int color)
_pixels[y * _width + x] = color;
_needRerender = true;
}

private async Task ChangeOpacityAction()
{
NumericPrompt numeric = new(Opacity)
{
Text = "Enter new opacity (%):"
};
var success = await numeric.ShowDialog<bool>(MainWindow.Handle);

if (success)
{
if(Opacity != numeric.Value)
{
_needRerender = true;
}
Opacity = numeric.Value;
RefreshRenderSource();
Subjects.LayerModified.OnNext(this);
}
}
}
1 change: 0 additions & 1 deletion Pixed/Pixed.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.3" />
<PackageReference Include="Avalonia.Themes.Simple" Version="11.1.3" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.1.0.4" />
<PackageReference Include="BigGustave" Version="1.0.6" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.1" />
<PackageReference Include="MateuszNejman.LZMA_SDK" Version="24.8.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
Expand Down
Binary file added Pixed/Resources/Icons/eye.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions Pixed/Utils/BitmapUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,27 @@ public static Avalonia.Media.Imaging.Bitmap ToAvaloniaBitmap(this Bitmap src)
return bitmap;
}

public static int[] ToPixelColors(this Bitmap bitmap)
{
int width = bitmap.Width;
int height = bitmap.Height;
int[] pixelArray = new int[width * height];

BitmapData data = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
int bytes = Math.Abs(data.Stride) * height;
byte[] byteData = new byte[bytes];
Marshal.Copy(data.Scan0, byteData, 0, bytes);
bitmap.UnlockBits(data);

for (int i = 0; i < byteData.Length; i += 4)
{
int pixel = BitConverter.ToInt32(byteData, i);
pixelArray[i / 4] = pixel;
}

return pixelArray;
}

public static Bitmap OpacityImage(this Bitmap src, float opacity)
{
Bitmap bmp = new(src.Width, src.Height);
Expand Down
Loading
Loading