using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; namespace Nikse.SubtitleEdit.Core { public class RunLengthTwoParts { public byte[] Buffer1 { get; set; } public byte[] Buffer2 { get; set; } public int Length => Buffer1.Length + Buffer2.Length; } public class NikseBitmap { private int _width; public int Width { get => _width; private set { _width = value; _widthX4 = _width * 4; } } public int Height { get; private set; } private byte[] _bitmapData; private int _pixelAddress; private int _widthX4; public NikseBitmap(int width, int height) { Width = width; Height = height; _bitmapData = new byte[Height * _widthX4]; } public NikseBitmap(int width, int height, byte[] bitmapData) { Width = width; Height = height; _bitmapData = bitmapData; } public NikseBitmap(Bitmap inputBitmap) { if (inputBitmap == null) { return; } Width = inputBitmap.Width; Height = inputBitmap.Height; if (inputBitmap.PixelFormat != PixelFormat.Format32bppArgb) { var newBitmap = new Bitmap(inputBitmap.Width, inputBitmap.Height, PixelFormat.Format32bppArgb); using (var gr = Graphics.FromImage(newBitmap)) { gr.DrawImage(inputBitmap, 0, 0); } inputBitmap.Dispose(); inputBitmap = newBitmap; } var bitmapData = inputBitmap.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); _bitmapData = new byte[bitmapData.Stride * Height]; Marshal.Copy(bitmapData.Scan0, _bitmapData, 0, _bitmapData.Length); inputBitmap.UnlockBits(bitmapData); } public NikseBitmap(NikseBitmap input) { Width = input.Width; Height = input.Height; _bitmapData = new byte[input._bitmapData.Length]; Buffer.BlockCopy(input._bitmapData, 0, _bitmapData, 0, _bitmapData.Length); } public void ReplaceYellowWithWhite() { var buffer = new byte[3]; buffer[0] = 255; buffer[1] = 255; buffer[2] = 255; for (int i = 0; i < _bitmapData.Length; i += 4) { if (_bitmapData[i + 3] > 200 && // Alpha _bitmapData[i + 2] > 199 && // Red _bitmapData[i + 1] > 190 && // Green _bitmapData[i] < 40) // Blue { Buffer.BlockCopy(buffer, 0, _bitmapData, i, 3); } } } public void ReplaceColor(int alpha, int red, int green, int blue, int alphaTo, int redTo, int greenTo, int blueTo) { var buffer = new byte[4]; buffer[0] = (byte)blueTo; buffer[1] = (byte)greenTo; buffer[2] = (byte)redTo; buffer[3] = (byte)alphaTo; for (int i = 0; i < _bitmapData.Length; i += 4) { if (_bitmapData[i + 3] == alpha && _bitmapData[i + 2] == red && _bitmapData[i + 1] == green && _bitmapData[i] == blue) { Buffer.BlockCopy(buffer, 0, _bitmapData, i, 4); } } } public void InvertColors() { for (int i = 0; i < _bitmapData.Length;) { _bitmapData[i] = (byte)~_bitmapData[i]; i++; _bitmapData[i] = (byte)~_bitmapData[i]; i++; _bitmapData[i] = (byte)~_bitmapData[i]; i += 2; } } public void ReplaceNonWhiteWithTransparent() { var buffer = new byte[4]; buffer[0] = 0; // B buffer[1] = 0; // G buffer[2] = 0; // R buffer[3] = 0; // A for (int i = 0; i < _bitmapData.Length; i += 4) { if (_bitmapData[i + 2] + _bitmapData[i + 1] + _bitmapData[i] < 300) { Buffer.BlockCopy(buffer, 0, _bitmapData, i, 4); } } } public void ReplaceTransparentWith(Color c) { var buffer = new byte[4]; buffer[0] = c.B; buffer[1] = c.G; buffer[2] = c.R; buffer[3] = c.A; for (int i = 0; i < _bitmapData.Length; i += 4) { if (_bitmapData[i + 3] < 10) { Buffer.BlockCopy(buffer, 0, _bitmapData, i, 4); } } } public void MakeOneColor(Color c) { var buffer = new byte[4]; buffer[0] = c.B; buffer[1] = c.G; buffer[2] = c.R; buffer[3] = c.A; var bufferTransparent = new byte[4]; bufferTransparent[0] = 0; bufferTransparent[1] = 0; bufferTransparent[2] = 0; bufferTransparent[3] = 0; for (int i = 0; i < _bitmapData.Length; i += 4) { Buffer.BlockCopy(_bitmapData[i] > 20 ? buffer : bufferTransparent, 0, _bitmapData, i, 4); } } private static Color GetOutlineColor(Color borderColor) { if (borderColor.R + borderColor.G + borderColor.B < 30) { return Color.FromArgb(200, 75, 75, 75); } return Color.FromArgb(150, borderColor.R, borderColor.G, borderColor.B); } /// /// Convert a x-color image to four colors, for e.g. DVD sub pictures. /// /// Background color /// Pattern color, normally white or yellow /// Emphasis 1, normally black or near black (border) /// public Color ConvertToFourColors(Color background, Color pattern, Color emphasis1, bool useInnerAntialize) { var backgroundBuffer = new byte[4]; backgroundBuffer[0] = background.B; backgroundBuffer[1] = background.G; backgroundBuffer[2] = background.R; backgroundBuffer[3] = background.A; var patternBuffer = new byte[4]; patternBuffer[0] = pattern.B; patternBuffer[1] = pattern.G; patternBuffer[2] = pattern.R; patternBuffer[3] = pattern.A; var emphasis1Buffer = new byte[4]; emphasis1Buffer[0] = emphasis1.B; emphasis1Buffer[1] = emphasis1.G; emphasis1Buffer[2] = emphasis1.R; emphasis1Buffer[3] = emphasis1.A; var emphasis2Buffer = new byte[4]; var emphasis2 = GetOutlineColor(emphasis1); if (!useInnerAntialize) { emphasis2Buffer[0] = emphasis2.B; emphasis2Buffer[1] = emphasis2.G; emphasis2Buffer[2] = emphasis2.R; emphasis2Buffer[3] = emphasis2.A; } for (int i = 0; i < _bitmapData.Length; i += 4) { int smallestDiff = 10000; byte[] buffer = backgroundBuffer; if (backgroundBuffer[3] == 0 && _bitmapData[i + 3] < 10) // transparent { } else { int patternDiff = Math.Abs(patternBuffer[0] - _bitmapData[i]) + Math.Abs(patternBuffer[1] - _bitmapData[i + 1]) + Math.Abs(patternBuffer[2] - _bitmapData[i + 2]) + Math.Abs(patternBuffer[3] - _bitmapData[i + 3]); if (patternDiff < smallestDiff) { smallestDiff = patternDiff; buffer = patternBuffer; } int emphasis1Diff = Math.Abs(emphasis1Buffer[0] - _bitmapData[i]) + Math.Abs(emphasis1Buffer[1] - _bitmapData[i + 1]) + Math.Abs(emphasis1Buffer[2] - _bitmapData[i + 2]) + Math.Abs(emphasis1Buffer[3] - _bitmapData[i + 3]); if (useInnerAntialize) { if (emphasis1Diff - 20 < smallestDiff) { buffer = emphasis1Buffer; } } else { if (emphasis1Diff < smallestDiff) { smallestDiff = emphasis1Diff; buffer = emphasis1Buffer; } int emphasis2Diff = Math.Abs(emphasis2Buffer[0] - _bitmapData[i]) + Math.Abs(emphasis2Buffer[1] - _bitmapData[i + 1]) + Math.Abs(emphasis2Buffer[2] - _bitmapData[i + 2]) + Math.Abs(emphasis2Buffer[3] - _bitmapData[i + 3]); if (emphasis2Diff < smallestDiff) { buffer = emphasis2Buffer; } else if (_bitmapData[i + 3] >= 10 && _bitmapData[i + 3] < 90) // anti-alias { buffer = emphasis2Buffer; } } } Buffer.BlockCopy(buffer, 0, _bitmapData, i, 4); } if (useInnerAntialize) { return VobSubAntialize(pattern, emphasis1); } return emphasis2; } private Color VobSubAntialize(Color pattern, Color emphasis1) { int r = (int)Math.Round(((pattern.R * 2.0 + emphasis1.R) / 3.0)); int g = (int)Math.Round(((pattern.G * 2.0 + emphasis1.G) / 3.0)); int b = (int)Math.Round(((pattern.B * 2.0 + emphasis1.B) / 3.0)); var antializeColor = Color.FromArgb(r, g, b); for (int y = 1; y < Height - 1; y++) { for (int x = 1; x < Width - 1; x++) { if (GetPixel(x, y) == pattern) { if (GetPixel(x - 1, y) == emphasis1 && GetPixel(x, y - 1) == emphasis1) { SetPixel(x, y, antializeColor); } else if (GetPixel(x - 1, y) == emphasis1 && GetPixel(x, y + 1) == emphasis1) { SetPixel(x, y, antializeColor); } else if (GetPixel(x + 1, y) == emphasis1 && GetPixel(x, y + 1) == emphasis1) { SetPixel(x, y, antializeColor); } else if (GetPixel(x + 1, y) == emphasis1 && GetPixel(x, y - 1) == emphasis1) { SetPixel(x, y, antializeColor); } } } } return antializeColor; } public RunLengthTwoParts RunLengthEncodeForDvd(Color background, Color pattern, Color emphasis1, Color emphasis2) { var backgroundBuffer = new byte[4]; backgroundBuffer[0] = background.B; backgroundBuffer[1] = background.G; backgroundBuffer[2] = background.R; backgroundBuffer[3] = background.A; var patternBuffer = new byte[4]; patternBuffer[0] = pattern.B; patternBuffer[1] = pattern.G; patternBuffer[2] = pattern.R; patternBuffer[3] = pattern.A; var emphasis1Buffer = new byte[4]; emphasis1Buffer[0] = emphasis1.B; emphasis1Buffer[1] = emphasis1.G; emphasis1Buffer[2] = emphasis1.R; emphasis1Buffer[3] = emphasis1.A; var emphasis2Buffer = new byte[4]; emphasis2Buffer[0] = emphasis2.B; emphasis2Buffer[1] = emphasis2.G; emphasis2Buffer[2] = emphasis2.R; emphasis2Buffer[3] = emphasis2.A; var bufferEqual = new byte[Width * Height]; var bufferUnEqual = new byte[Width * Height]; int indexBufferEqual = 0; int indexBufferUnEqual = 0; _pixelAddress = -4; for (int y = 0; y < Height; y++) { int index; byte[] buffer; if (y % 2 == 0) { index = indexBufferEqual; buffer = bufferEqual; } else { index = indexBufferUnEqual; buffer = bufferUnEqual; } var indexHalfNibble = false; var lastColor = -1; var count = 0; for (int x = 0; x < Width; x++) { int color = GetDvdColor(patternBuffer, emphasis1Buffer, emphasis2Buffer); if (lastColor == -1) { lastColor = color; count = 1; } else if (lastColor == color && count < 64) // only allow up to 63 run-length (for SubtitleCreator compatibility) { count++; } else { WriteRle(ref indexHalfNibble, lastColor, count, ref index, buffer); lastColor = color; count = 1; } } if (count > 0) { WriteRle(ref indexHalfNibble, lastColor, count, ref index, buffer); } if (indexHalfNibble) { index++; } if (y % 2 == 0) { indexBufferEqual = index; bufferEqual = buffer; } else { indexBufferUnEqual = index; bufferUnEqual = buffer; } } var twoParts = new RunLengthTwoParts { Buffer1 = new byte[indexBufferEqual] }; Buffer.BlockCopy(bufferEqual, 0, twoParts.Buffer1, 0, indexBufferEqual); twoParts.Buffer2 = new byte[indexBufferUnEqual + 2]; Buffer.BlockCopy(bufferUnEqual, 0, twoParts.Buffer2, 0, indexBufferUnEqual); return twoParts; } private static void WriteRle(ref bool indexHalfNibble, int lastColor, int count, ref int index, byte[] buffer) { if (count <= 0b00000011) // 1-3 repetitions { WriteOneNibble(buffer, count, lastColor, ref index, ref indexHalfNibble); } else if (count <= 0b00001111) // 4-15 repetitions { WriteTwoNibbles(buffer, count, lastColor, ref index, indexHalfNibble); } else if (count <= 0b00111111) // 4-15 repetitions { WriteThreeNibbles(buffer, count, lastColor, ref index, ref indexHalfNibble); // 16-63 repetitions } else // 64-255 repetitions { int factor = count / 255; for (int i = 0; i < factor; i++) { WriteFourNibbles(buffer, 0xff, lastColor, ref index, indexHalfNibble); } int rest = count % 255; if (rest > 0) { WriteFourNibbles(buffer, rest, lastColor, ref index, indexHalfNibble); } } } private static void WriteFourNibbles(byte[] buffer, int count, int color, ref int index, bool indexHalfNibble) { int n = (count << 2) + color; if (indexHalfNibble) { index++; var firstNibble = (byte)(n >> 4); buffer[index] = firstNibble; index++; var secondNibble = (byte)((n & 0b00001111) << 4); buffer[index] = secondNibble; } else { var firstNibble = (byte)(n >> 8); buffer[index] = firstNibble; index++; var secondNibble = (byte)(n & 0b11111111); buffer[index] = secondNibble; index++; } } private static void WriteThreeNibbles(byte[] buffer, int count, int color, ref int index, ref bool indexHalfNibble) { //Value Bits n=length, c=color //16-63 12 0 0 0 0 n n n n n n c c (one and a half byte) var n = (ushort)((count << 2) + color); if (indexHalfNibble) { index++; // there should already zeroes in last nibble buffer[index] = (byte)n; index++; } else { buffer[index] = (byte)(n >> 4); index++; buffer[index] = (byte)((n & 0b00011111) << 4); } indexHalfNibble = !indexHalfNibble; } private static void WriteTwoNibbles(byte[] buffer, int count, int color, ref int index, bool indexHalfNibble) { //Value Bits n=length, c=color //4-15 8 0 0 n n n n c c (one byte) var n = (byte)((count << 2) + color); if (indexHalfNibble) { var firstNibble = (byte)(n >> 4); buffer[index] = (byte)(buffer[index] | firstNibble); var secondNibble = (byte)((n & 0b00001111) << 4); index++; buffer[index] = secondNibble; } else { buffer[index] = n; index++; } } private static void WriteOneNibble(byte[] buffer, int count, int color, ref int index, ref bool indexHalfNibble) { var n = (byte)((count << 2) + color); if (indexHalfNibble) { buffer[index] = (byte)(buffer[index] | n); index++; } else { buffer[index] = (byte)(n << 4); } indexHalfNibble = !indexHalfNibble; } private int GetDvdColor(byte[] pattern, byte[] emphasis1, byte[] emphasis2) { _pixelAddress += 4; int a = _bitmapData[_pixelAddress + 3]; int r = _bitmapData[_pixelAddress + 2]; int g = _bitmapData[_pixelAddress + 1]; int b = _bitmapData[_pixelAddress]; if (pattern[0] == b && pattern[1] == g && pattern[2] == r && pattern[3] == a) { return 1; } if (emphasis1[0] == b && emphasis1[1] == g && emphasis1[2] == r && emphasis1[3] == a) { return 2; } if (emphasis2[0] == b && emphasis2[1] == g && emphasis2[2] == r && emphasis2[3] == a) { return 3; } return 0; } public int CropTransparentSidesAndBottom(int maximumCropping, bool bottom) { int leftStart = 0; bool done = false; int x = 0; int y; while (!done && x < Width) { y = 0; while (!done && y < Height) { int alpha = GetAlpha(x, y); if (alpha != 0) { done = true; leftStart = x; if (leftStart > maximumCropping) { leftStart = leftStart - maximumCropping; } if (leftStart < 0) { leftStart = 0; } } y++; } x++; } int rightEnd = Width - 1; done = false; x = Width - 1; while (!done && x >= 0) { y = 0; while (!done && y < Height) { int alpha = GetAlpha(x, y); if (alpha != 0) { done = true; rightEnd = x; if (Width - rightEnd > maximumCropping) { rightEnd += maximumCropping; } if (rightEnd >= Width) { rightEnd = Width - 1; } } y++; } x--; } //crop bottom done = false; int newHeight = Height; if (bottom) { y = Height - 1; while (!done && y > 0) { x = 0; while (!done && x < Width) { int alpha = GetAlpha(x, y); if (alpha != 0) { done = true; newHeight = y + maximumCropping + 1; if (newHeight > Height) { newHeight = Height; } } x++; } y--; } } if (leftStart < 2 && rightEnd >= Width - 3) { return 0; } int newWidth = rightEnd - leftStart + 1; if (newWidth <= 0) { return 0; } var newBitmapData = new byte[newWidth * newHeight * 4]; int index = 0; var newWidthX4 = 4 * newWidth; for (y = 0; y < newHeight; y++) { int pixelAddress = (leftStart * 4) + (y * _widthX4); Buffer.BlockCopy(_bitmapData, pixelAddress, newBitmapData, index, newWidthX4); index += newWidthX4; } Width = newWidth; Height = newHeight; _bitmapData = newBitmapData; return leftStart; } /// Pixels cropped left public int CropSidesAndBottom(int maximumCropping, Color transparentColor, bool bottom) { int leftStart = 0; bool done = false; int x = 0; int y; while (!done && x < Width) { y = 0; while (!done && y < Height) { var c = GetPixel(x, y); if (c != transparentColor) { done = true; leftStart = x; leftStart -= maximumCropping; if (leftStart < 0) { leftStart = 0; } } y++; } x++; } int rightEnd = Width - 1; done = false; x = Width - 1; while (!done && x >= 0) { y = 0; while (!done && y < Height) { var c = GetPixel(x, y); if (c != transparentColor) { done = true; rightEnd = x; rightEnd += maximumCropping; if (rightEnd >= Width) { rightEnd = Width - 1; } } y++; } x--; } //crop bottom done = false; int newHeight = Height; if (bottom) { y = Height - 1; while (!done && y > 0) { x = 0; while (!done && x < Width) { var c = GetPixel(x, y); if (c != transparentColor) { done = true; newHeight = y + maximumCropping; if (newHeight > Height) { newHeight = Height; } } x++; } y--; } } if (leftStart < 2 && rightEnd >= Width - 3) { return 0; } int newWidth = rightEnd - leftStart + 1; if (newWidth <= 0) { return 0; } var newBitmapData = new byte[newWidth * newHeight * 4]; int index = 0; var newWidthX4 = 4 * newWidth; for (y = 0; y < newHeight; y++) { int pixelAddress = (leftStart * 4) + (y * _widthX4); Buffer.BlockCopy(_bitmapData, pixelAddress, newBitmapData, index, newWidthX4); index += newWidthX4; } Width = newWidth; Height = newHeight; _bitmapData = newBitmapData; return leftStart; } public void CropTop(int maximumCropping, Color transparentColor) { bool done = false; int newTop = 0; int y = 0; while (!done && y < Height) { var x = 0; while (!done && x < Width) { var c = GetPixel(x, y); if (c != transparentColor && !(c.A == 0 && transparentColor.A == 0)) { done = true; newTop = y - maximumCropping; if (newTop < 0) { newTop = 0; } } x++; } y++; } if (newTop == 0) { return; } int newHeight = Height - newTop; var newBitmapData = new byte[newHeight * _widthX4]; int index = 0; for (y = newTop; y < Height; y++) { int pixelAddress = y * _widthX4; Buffer.BlockCopy(_bitmapData, pixelAddress, newBitmapData, index, _widthX4); index += _widthX4; } Height = newHeight; _bitmapData = newBitmapData; } public int CropTopTransparent(int maximumCropping) { bool done = false; int newTop = 0; int y = 0; while (!done && y < Height) { var x = 0; while (!done && x < Width) { int alpha = GetAlpha(x, y); if (alpha > 10) { done = true; newTop = y - maximumCropping; if (newTop < 0) { newTop = 0; } } x++; } y++; } if (newTop == 0) { return 0; } int newHeight = Height - newTop; var newBitmapData = new byte[newHeight * _widthX4]; int index = 0; for (y = newTop; y < Height; y++) { int pixelAddress = y * _widthX4; Buffer.BlockCopy(_bitmapData, pixelAddress, newBitmapData, index, _widthX4); index += _widthX4; } Height = newHeight; _bitmapData = newBitmapData; return newTop; } public int CalcBottomCropping(Color transparentColor) { int y = Height - 1; int cropping = 0; while (y > 0) { int x = 0; while (x < Width) { var c = GetPixel(x, y); if (c != transparentColor && c.A != 0) { return cropping; } x++; } y--; cropping++; } return cropping; } public void Fill(Color color) { var buffer = new byte[4]; buffer[0] = color.B; buffer[1] = color.G; buffer[2] = color.R; buffer[3] = color.A; for (int i = 0; i < _bitmapData.Length; i += 4) { Buffer.BlockCopy(buffer, 0, _bitmapData, i, 4); } } public int GetAlpha(int x, int y) { return _bitmapData[(x * 4) + (y * _widthX4) + 3]; } public int GetAlpha(int index) { return _bitmapData[index]; } public Color GetPixel(int x, int y) { _pixelAddress = (x * 4) + (y * _widthX4); return Color.FromArgb(_bitmapData[_pixelAddress + 3], _bitmapData[_pixelAddress + 2], _bitmapData[_pixelAddress + 1], _bitmapData[_pixelAddress]); } public byte[] GetPixelColors(int x, int y) { _pixelAddress = (x * 4) + (y * _widthX4); return new[] { _bitmapData[_pixelAddress + 3], _bitmapData[_pixelAddress + 2], _bitmapData[_pixelAddress + 1], _bitmapData[_pixelAddress] }; } public Color GetPixelNext() { _pixelAddress += 4; return Color.FromArgb(_bitmapData[_pixelAddress + 3], _bitmapData[_pixelAddress + 2], _bitmapData[_pixelAddress + 1], _bitmapData[_pixelAddress]); } public void SetPixel(int x, int y, Color color) { _pixelAddress = (x * 4) + (y * _widthX4); _bitmapData[_pixelAddress] = color.B; _bitmapData[_pixelAddress + 1] = color.G; _bitmapData[_pixelAddress + 2] = color.R; _bitmapData[_pixelAddress + 3] = color.A; } public Bitmap GetBitmap() { var bitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb); var bitmapData = bitmap.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); var destination = bitmapData.Scan0; Marshal.Copy(_bitmapData, 0, destination, _bitmapData.Length); bitmap.UnlockBits(bitmapData); return bitmap; } private static int FindBestMatch(Color color, List palette, out int maxDiff) { int smallestDiff = 1000; int smallestDiffIndex = -1; int i = 0; foreach (var pc in palette) { int diff = Math.Abs(pc.A - color.A) + Math.Abs(pc.R - color.R) + Math.Abs(pc.G - color.G) + Math.Abs(pc.B - color.B); if (diff < smallestDiff) { smallestDiff = diff; smallestDiffIndex = i; if (smallestDiff < 4) { maxDiff = smallestDiff; return smallestDiffIndex; } } i++; } maxDiff = smallestDiff; return smallestDiffIndex; } public Bitmap ConvertTo8BitsPerPixel() { var newBitmap = new Bitmap(Width, Height, PixelFormat.Format8bppIndexed); var palette = new List { Color.Transparent }; var bPalette = newBitmap.Palette; var entries = bPalette.Entries; for (int i = 0; i < newBitmap.Palette.Entries.Length; i++) { entries[i] = Color.Transparent; } var data = newBitmap.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); var bytes = new byte[data.Height * data.Stride]; Marshal.Copy(data.Scan0, bytes, 0, bytes.Length); for (int y = 0; y < Height; y++) { for (int x = 0; x < Width; x++) { var c = GetPixel(x, y); if (c.A < 5) { bytes[y * data.Stride + x] = 0; } else { int index = FindBestMatch(c, palette, out var maxDiff); if (index == -1 && palette.Count < 255) { index = palette.Count; entries[index] = c; palette.Add(c); bytes[y * data.Stride + x] = (byte)index; } else if (palette.Count < 200 && maxDiff > 5) { index = palette.Count; entries[index] = c; palette.Add(c); bytes[y * data.Stride + x] = (byte)index; } else if (palette.Count < 255 && maxDiff > 15) { index = palette.Count; entries[index] = c; palette.Add(c); bytes[y * data.Stride + x] = (byte)index; } else if (index >= 0) { bytes[y * data.Stride + x] = (byte)index; } } } } Marshal.Copy(bytes, 0, data.Scan0, bytes.Length); newBitmap.UnlockBits(data); newBitmap.Palette = bPalette; return newBitmap; } public NikseBitmap CopyRectangle(Rectangle section) { if (section.Bottom > Height) { section = new Rectangle(section.Left, section.Top, section.Width, Height - section.Top); } if (section.Width + section.Left > Width) { section = new Rectangle(section.Left, section.Top, Width - section.Left, section.Height); } var newBitmapData = new byte[section.Width * section.Height * 4]; int index = 0; var sectionWidthX4 = 4 * section.Width; var sectionLeftX4 = 4 * section.Left; for (int y = section.Top; y < section.Bottom; y++) { int pixelAddress = sectionLeftX4 + (y * _widthX4); Buffer.BlockCopy(_bitmapData, pixelAddress, newBitmapData, index, sectionWidthX4); index += sectionWidthX4; } return new NikseBitmap(section.Width, section.Height, newBitmapData); } /// /// Returns brightest color (not white though) /// /// Brightest color, if not found or if brightes color is white, then Color.Transparent is returned public Color GetBrightestColorWhiteIsTransparent() { int max = Width * Height - 4; var brightest = Color.Black; for (int i = 0; i < max; i++) { var c = GetPixelNext(); if (c.A > 220 && c.R + c.G + c.B > 200 && c.R + c.G + c.B > brightest.R + brightest.G + brightest.B) { brightest = c; } } if (IsColorClose(Color.White, brightest, 40)) { return Color.Transparent; } if (IsColorClose(Color.Black, brightest, 10)) { return Color.Transparent; } return brightest; } /// /// Returns brightest color /// /// Brightest color public Color GetBrightestColor() { int max = Width * Height - 4; var brightest = Color.Black; for (int i = 0; i < max; i++) { var c = GetPixelNext(); if (c.A > 220 && c.R + c.G + c.B > 200 && c.R + c.G + c.B > brightest.R + brightest.G + brightest.B) { brightest = c; } } return brightest; } private static bool IsColorClose(Color color1, Color color2, int maxDiff) { if (Math.Abs(color1.R - color2.R) < maxDiff && Math.Abs(color1.G - color2.G) < maxDiff && Math.Abs(color1.B - color2.B) < maxDiff) { return true; } return false; } public void GrayScale() { for (int i = 0; i < _bitmapData.Length; i += 4) { int medium = Convert.ToInt32((_bitmapData[i + 2] + _bitmapData[i + 1] + _bitmapData[i]) * 1.5 / 3.0 + 2); if (medium > byte.MaxValue) { medium = byte.MaxValue; } _bitmapData[i + 2] = _bitmapData[i + 1] = _bitmapData[i] = (byte)medium; } } /// /// Make pixels with some transparency completely transparent /// /// Min alpha value, 0=transparent, 255=fully visible public void MakeBackgroundTransparent(int minAlpha) { var buffer = new byte[4]; buffer[0] = 0; // B buffer[1] = 0; // G buffer[2] = 0; // R buffer[3] = 0; // A for (int i = 0; i < _bitmapData.Length; i += 4) { if (_bitmapData[i + 3] < minAlpha) { Buffer.BlockCopy(buffer, 0, _bitmapData, i, 4); } } } public void MakeTwoColor(int minRgb) { var buffer = new byte[4]; buffer[0] = 0; // B buffer[1] = 0; // G buffer[2] = 0; // R buffer[3] = 0; // A var bufferWhite = new byte[4]; bufferWhite[0] = 255; // B bufferWhite[1] = 255; // G bufferWhite[2] = 255; // R bufferWhite[3] = 255; // A for (int i = 0; i < _bitmapData.Length; i += 4) { if (_bitmapData[i + 3] < 1 || _bitmapData[i + 0] + _bitmapData[i + 1] + _bitmapData[i + 2] < minRgb) { Buffer.BlockCopy(buffer, 0, _bitmapData, i, 4); } else { Buffer.BlockCopy(bufferWhite, 0, _bitmapData, i, 4); } } } public void MakeTwoColor(int minRgb, Color background, Color foreground) { var bufferBackground = new byte[4]; bufferBackground[0] = background.B; // B bufferBackground[1] = background.G; // G bufferBackground[2] = background.R; // R bufferBackground[3] = 255; // A var bufferForeground = new byte[4]; bufferForeground[0] = foreground.B; // B bufferForeground[1] = foreground.G; // G bufferForeground[2] = foreground.R; // R bufferForeground[3] = 255; // A for (int i = 0; i < _bitmapData.Length; i += 4) { if (_bitmapData[i + 3] < 1 || _bitmapData[i + 0] + _bitmapData[i + 1] + _bitmapData[i + 2] < minRgb) { Buffer.BlockCopy(bufferBackground, 0, _bitmapData, i, 4); } else { Buffer.BlockCopy(bufferForeground, 0, _bitmapData, i, 4); } } } private static readonly byte[] EmptyByteArray = new byte[100000]; public void MakeVerticalLinePartTransparent(int xStart, int xEnd, int y) { if (xEnd > Width - 1) { xEnd = Width - 1; } if (xStart < 0) { xStart = 0; } int startIndex = (xStart * 4) + (y * _widthX4); int endIndex = (xEnd * 4) + (y * _widthX4) + 4; int length = endIndex - startIndex; Buffer.BlockCopy(EmptyByteArray, 0, _bitmapData, startIndex, length); } public void AddTransparentLineRight() { int newWidth = Width + 1; var newBitmapData = new byte[newWidth * Height * 4]; int index = 0; for (int y = 0; y < Height; y++) { int pixelAddress = (0 * 4) + (y * _widthX4); Buffer.BlockCopy(_bitmapData, pixelAddress, newBitmapData, index, _widthX4); index += 4 * newWidth; } Width = newWidth; _bitmapData = newBitmapData; for (int y = 0; y < Height; y++) { SetPixel(Width - 1, y, Color.Transparent); } } public void AddMargin(int margin) { int newWidth = Width + margin * 2; int newHeight = Height + margin * 2; var newBitmapData = new byte[newWidth * newHeight * 4]; var newWidthX4 = newWidth * 4; var marginX4 = margin * 4; for (int y = 0; y < Height; y++) { int pixelAddress = y * _widthX4; int index = marginX4 + (y + margin) * newWidthX4; Buffer.BlockCopy(_bitmapData, pixelAddress, newBitmapData, index, _widthX4); } Width = newWidth; Height = newHeight; _bitmapData = newBitmapData; } public void SaveAsTarga(string fileName) { // TGA header (18-byte fixed header) byte[] header = { 0, // ID length (1 bytes) 0, // no color map (1 bytes) 2, // uncompressed, true color (1 bytes) 0, 0, // Color map First Entry Index 0, 0, // Color map Length 0, // Color map Entry Size 0, 0, 0, 0, // x and y origin (byte)(Width & 0x00FF), (byte)((Width & 0xFF00) >> 8), (byte)(Height & 0x00FF), (byte)((Height & 0xFF00) >> 8), 32, // pixel depth - 32=32 bit bitmap 0 // Image Descriptor }; var pixels = new byte[_bitmapData.Length]; int offsetDest = 0; for (int y = Height - 1; y >= 0; y--) // takes lines from bottom lines to top (mirrored horizontally) { for (int x = 0; x < Width; x++) { var c = GetPixel(x, y); pixels[offsetDest] = c.B; pixels[offsetDest + 1] = c.G; pixels[offsetDest + 2] = c.R; pixels[offsetDest + 3] = c.A; offsetDest += 4; } } using (var fileStream = File.Create(fileName)) { fileStream.Write(header, 0, header.Length); fileStream.Write(pixels, 0, pixels.Length); } } /// /// Horizontal line. /// public bool IsLineTransparent(int y) { int max = (_width * 4) + (y * _widthX4) + 3; for (int pos = y * _widthX4 + 3; pos < max; pos += 4) { if (_bitmapData[pos] != 0) { return false; } } return true; } public bool IsVerticalLineTransparent(int x) { for (int y = 0; y < Height; y++) { if (GetAlpha(x, y) > 0) { return false; } } return true; } public bool IsImageOnlyTransparent() { for (int i = 0; i < _bitmapData.Length; i += 4) { if (_bitmapData[i] != 0) // check alpha { return false; } } return true; } public int GetNonTransparentHeight() { var startY = 0; int transparentBottomPixels = 0; for (int y = 0; y < Height; y++) { var isLineTransparent = IsLineTransparent(y); if (startY == y && isLineTransparent) { startY++; continue; } if (isLineTransparent) { transparentBottomPixels++; } else { transparentBottomPixels = 0; } } return Height - startY - transparentBottomPixels; } public int GetNonTransparentWidth() { var startX = 0; int transparentPixelsRight = 0; for (int x = 0; x < Width; x++) { var isLineTransparent = IsVerticalLineTransparent(x); if (startX == x && isLineTransparent) { startX++; continue; } if (isLineTransparent) { transparentPixelsRight++; } else { transparentPixelsRight = 0; } } return Width - startX - transparentPixelsRight; } public void EnsureEvenLines(Color fillColor) { if (Width % 2 == 0 && Height % 2 == 0) { return; } int newWidth = Width; bool widthChanged = false; if (Width % 2 != 0) { newWidth++; widthChanged = true; } int newHeight = Height; bool heightChanged = false; if (Height % 2 != 0) { newHeight++; heightChanged = true; } var newBitmapData = new byte[newWidth * newHeight * 4]; var newWidthX4 = 4 * newWidth; int index = 0; for (int y = 0; y < Height; y++) { int pixelAddress = y * _widthX4; Buffer.BlockCopy(_bitmapData, pixelAddress, newBitmapData, index, _widthX4); index += newWidthX4; } Width = newWidth; Height = newHeight; _bitmapData = newBitmapData; if (widthChanged) { for (var y = 0; y < Height; y++) { SetPixel(Width - 1, y, fillColor); } } if (heightChanged) { for (var x = 0; x < Width; x++) { SetPixel(x, Height - 1, fillColor); } } } public bool IsEqualTo(NikseBitmap bitmap) { if (Width != bitmap.Width || Height != bitmap.Height) { return false; } if (Width == bitmap.Width && Height == bitmap.Height && Width == 0 && Height == 0) { return true; } for (int i = 0; i < _bitmapData.Length; i++) { if (_bitmapData[i] != bitmap._bitmapData[i]) { return false; } } return true; } public void SetTransparentTo(Color transparent) { var buffer = new byte[4]; buffer[0] = transparent.B; buffer[1] = transparent.G; buffer[2] = transparent.R; buffer[3] = transparent.A; for (var i = 0; i < _bitmapData.Length; i += 4) { if (_bitmapData[i + 3] == 0) { Buffer.BlockCopy(buffer, 0, _bitmapData, i, 4); } } } } }