// // PictureObjectModel.cs - provides an ObjectModel for manipulating image files // // Author: // Michael Becker // // Copyright (c) 2011-2020 Mike Becker's Software // // 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 3 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 . using System; using System.Collections.Generic; using MBS.Framework.Drawing; using UniversalEditor.ObjectModels.FileSystem; namespace UniversalEditor.ObjectModels.Multimedia.Picture { /// /// Provides an for manipulating image files. /// public class PictureObjectModel : ObjectModel { public class PictureObjectModelCollection : System.Collections.ObjectModel.Collection { } private PositionVector2 lastAddedLocation = new PositionVector2(0, 0); public int Stride { get { // thanks https://stackoverflow.com/questions/2185944/why-must-stride-in-the-system-drawing-bitmap-constructor-be-a-multiple-of-4 return GetStride(32); } } public List ColorMap { get; } = new List(); public event DataRequestEventHandler DataRequest; protected virtual void OnDataRequest(DataRequestEventArgs e) { DataRequest?.Invoke(this, e); } /// /// Generates a color map as a of the s used in this . /// /// The color map of colors used in this . public List GenerateColorMap() { List colorMap = new List(); for (int x = 0; x < bitmapData.Length; x++) { for (int y = 0; y < bitmapData[x].Length; y++) { Color color = bitmapData[x][y]; if (!colorMap.Contains(color)) colorMap.Add(color); } } return colorMap; } public void SetPixel(Color color) { if (lastAddedLocation.X >= mvarWidth && lastAddedLocation.Y >= mvarHeight) { throw new InvalidOperationException("Out of image space. Try resizing the image"); } if (lastAddedLocation.X >= mvarWidth) { lastAddedLocation.X = 0; lastAddedLocation.Y++; } SetPixel(color, (int)lastAddedLocation.X, (int)lastAddedLocation.Y); lastAddedLocation.X++; } public void SetPixel(Color color, int x, int y) { if (x >= mvarWidth || y >= mvarHeight) { throw new InvalidOperationException("Out of image space. Try resizing the image"); } int index = (x + (y * mvarWidth)) * 4; bitmapData[x][y] = color; hasData = true; int realIndex = (int)(index / 4); } public void ClearPixel(int x, int y) { int index = (int)((lastAddedLocation.X + (lastAddedLocation.Y * mvarWidth)) * 4); bitmapData[x][y] = Color.Empty; int realIndex = (int)(index / 4); } public Color GetPixel(int x, int y) { if (x >= mvarWidth || y >= mvarHeight) { throw new InvalidOperationException("Out of image space. Try resizing the image"); } if (!hasData) { DataRequestEventArgs e = new DataRequestEventArgs(); OnDataRequest(e); } int index = (x + (y * mvarWidth)) * 4; int realIndex = (int)(index / 4); if (!bitmapData[x][y].IsEmpty) { Color color = bitmapData[x][y]; return color; } return Color.Empty; } public Color GetPixel(PositionVector2 point) { return GetPixel((int)point.X, (int)point.Y); } private Color[][] bitmapData = new Color[0][]; private bool hasData = false; public PictureObjectModel() { } public PictureObjectModel(int width, int height) { InitializeBitmapData(); mvarWidth = width; mvarHeight = height; } private static void InitializeBitmapData(ref Color[][] array, int width, int height) { array = new Color[width][]; for (int i = 0; i < width; i++) { array[i] = new Color[height]; } } private void InitializeBitmapData() { InitializeBitmapData(ref bitmapData, Width, Height); } private int mvarWidth = 1; public int Width { get { return mvarWidth; } set { mvarWidth = value; InitializeBitmapData(); } } private int mvarHeight = 1; public int Height { get { return mvarHeight; } set { mvarHeight = value; InitializeBitmapData(); } } public Dimension2D Size { get { return new Dimension2D(mvarWidth, mvarHeight); } set { mvarWidth = (int)value.Width; mvarHeight = (int)value.Height; InitializeBitmapData(); } } protected override ObjectModelReference MakeReferenceInternal() { ObjectModelReference omr = new ObjectModelReference(GetType(), new Guid("{4ae4c9ac-d8ab-4f7d-8d45-5e7fb8c475cc}")); omr.Path = new string[] { "Multimedia", "Picture", "Single Picture" }; return omr; } public override void Clear() { ColorMap.Clear(); Width = 1; Height = 1; } public override void CopyTo(ObjectModel destination) { PictureObjectModel clone = (destination as PictureObjectModel); clone.Width = mvarWidth; clone.Height = mvarHeight; clone.bitmapData = (bitmapData.Clone() as Color[][]); } public byte[] ToByteArray(PixelFormat format) { if (!hasData) { DataRequestEventArgs e = new DataRequestEventArgs(); OnDataRequest(e); } // memory goes from left to right, top to bottom byte[] data = new byte[mvarWidth * mvarHeight * 4]; int i = 0; for (int h = 0; h < mvarHeight; h++) { for (int w = 0; w < mvarWidth; w++) { int index = (w + (h * mvarWidth)) * 4; int realIndex = (int)(index / 4); switch (format) { case PixelFormat.BGRA: { // BGRA layout data[i + 0] = bitmapData[w][h].GetBlueByte(); data[i + 1] = bitmapData[w][h].GetGreenByte(); data[i + 2] = bitmapData[w][h].GetRedByte(); data[i + 3] = bitmapData[w][h].GetAlphaByte(); i += 4; break; } case PixelFormat.RGBA: { // RGBA layout data[i + 0] = bitmapData[w][h].GetRedByte(); data[i + 1] = bitmapData[w][h].GetGreenByte(); data[i + 2] = bitmapData[w][h].GetBlueByte(); data[i + 3] = bitmapData[w][h].GetAlphaByte(); i += 4; break; } } } } return data; } public static PictureObjectModel FromByteArray(byte[] data, int width, int height) { if (width == 0 || height == 0) return null; PictureObjectModel pic = new PictureObjectModel(); pic.LoadByteArray(data, width, height); return pic; } private void LoadByteArray(byte[] data, int width, int height) { Width = width; Height = height; LoadByteArray(data); } private void LoadByteArray(byte[] data) { // memory goes from left to right, top to bottom int i = 0; for (int h = 0; h < Height; h++) { for (int w = 0; w < Width; w++) { byte b = data[i]; byte g = data[i + 1]; byte r = data[i + 2]; byte a = data[i + 3]; Color color = Color.FromRGBAByte(r, g, b, a); SetPixel(color, w, h); i += 4; } } } public int GetStride(int bitsPerPixel) { // this calculation is CORRECT (at least on gdkpixbuf) so ***DON'T TOUCH IT*** int bytesPerPixel = (int)((double)bitsPerPixel / 8); // (bitsPerPixel + 7) / 8; // wtf??? int stride = 4 * ((Width * bytesPerPixel + 3) / 4); return stride; } public void Rotate(int degrees) { int oldheight = Height; int oldwidth = Width; // fuck this ain't workin if (degrees % 360 == 0) { return; } switch (degrees) { case -90: { Color[][] oldpixels = bitmapData; Size = new Dimension2D(Height, Width); Color[][] pixels = null; InitializeBitmapData(ref pixels, Width, Height); // 1 2 3 // 4 5 6 // => // 3 6 // 2 5 // 1 4 int x1 = 0, y1 = 0; for (int y = 0; y < oldheight; y++) { for (int x = oldwidth - 1; x >= 0; x--) { pixels[x1][y1] = oldpixels[x][y]; y1++; } y1 = 0; x1++; } bitmapData = pixels; break; } case 90: { Color[][] oldpixels = bitmapData; Size = new Dimension2D(Height, Width); Color[][] pixels = null; InitializeBitmapData(ref pixels, Width, Height); // 1 2 3 // 4 5 6 // => // 3 6 // 2 5 // 1 4 int x1 = 0, y1 = 0; for (int y = 0; y < oldheight; y++) { for (int x = oldwidth - 1; x >= 0; x--) { pixels[x1][y1] = oldpixels[x][y]; y1++; } y1 = 0; x1++; } bitmapData = pixels; break; } case 180: { break; } case 270: { break; } } } } }