internal unsafe void Vertical(BitmapBase src, BitmapBase dest, BlurEdgeMode edgeMode) { for (int x = 0; x < src.Width; x++) { byte *colSource = src.Data + x * 4; byte *colResult = dest.Data + x * 4; for (int y = 0; y < src.Height; y++) { int rSum = 0, gSum = 0, bSum = 0, aSum = 0; for (int k = 0, ySrc = y - _kernel.Length / 2; k < _kernel.Length; k++, ySrc++) { int yRead = ySrc; if (yRead < 0 || yRead >= src.Height) { switch (edgeMode) { case BlurEdgeMode.Transparent: continue; case BlurEdgeMode.Same: yRead = yRead < 0 ? 0 : src.Height - 1; break; case BlurEdgeMode.Wrap: yRead = Ut.ModPositive(yRead, src.Height); break; case BlurEdgeMode.Mirror: if (yRead < 0) { yRead = -yRead - 1; } yRead = yRead % (2 * src.Height); if (yRead >= src.Height) { yRead = 2 * src.Height - yRead - 1; } break; } } yRead *= src.Stride; bSum += _kernel[k] * colSource[yRead + 0]; gSum += _kernel[k] * colSource[yRead + 1]; rSum += _kernel[k] * colSource[yRead + 2]; aSum += _kernel[k] * colSource[yRead + 3]; } int yWrite = y * dest.Stride; colResult[yWrite + 0] = (byte)(bSum / _kernelSum); colResult[yWrite + 1] = (byte)(gSum / _kernelSum); colResult[yWrite + 2] = (byte)(rSum / _kernelSum); colResult[yWrite + 3] = (byte)(aSum / _kernelSum); } } }
internal unsafe void Horizontal(BitmapBase src, BitmapBase dest, BlurEdgeMode edgeMode) { for (int y = 0; y < src.Height; y++) { byte *rowSource = src.Data + y * src.Stride; byte *rowResult = dest.Data + y * dest.Stride; for (int x = 0; x < src.Width; x++) { int rSum = 0, gSum = 0, bSum = 0, aSum = 0; for (int k = 0, xSrc = x - _kernel.Length / 2; k < _kernel.Length; k++, xSrc++) { int xRead = xSrc; if (xRead < 0 || xRead >= src.Width) { switch (edgeMode) { case BlurEdgeMode.Transparent: continue; case BlurEdgeMode.Same: xRead = xRead < 0 ? 0 : src.Width - 1; break; case BlurEdgeMode.Wrap: xRead = Ut.ModPositive(xRead, src.Width); break; case BlurEdgeMode.Mirror: if (xRead < 0) { xRead = -xRead - 1; } xRead = xRead % (2 * src.Width); if (xRead >= src.Width) { xRead = 2 * src.Width - xRead - 1; } break; } } xRead <<= 2; // * 4 bSum += _kernel[k] * rowSource[xRead + 0]; gSum += _kernel[k] * rowSource[xRead + 1]; rSum += _kernel[k] * rowSource[xRead + 2]; aSum += _kernel[k] * rowSource[xRead + 3]; } int xWrite = x << 2; // * 4 rowResult[xWrite + 0] = (byte)(bSum / _kernelSum); rowResult[xWrite + 1] = (byte)(gSum / _kernelSum); rowResult[xWrite + 2] = (byte)(rSum / _kernelSum); rowResult[xWrite + 3] = (byte)(aSum / _kernelSum); } } }
unsafe private static void Resample1D(BitmapBase bmpDest, BitmapBase bmpSrc, int transparentOffset, ContributorEntry[] contrib, int alongSize, int crossSize, bool horz) { using (bmpSrc.UseRead()) using (bmpDest.UseWrite()) { byte *srcBytes = bmpSrc.Data + transparentOffset; for (int crossCoord = 0; crossCoord < crossSize; ++crossCoord) { for (int alongCoord = 0; alongCoord < alongSize; ++alongCoord) { for (int channel = 0; channel < 4; ++channel) { double intensity = 0; double wsum = 0; for (int j = 0; j < contrib[alongCoord].SrcPixelCount; j++) { int contribCoord = contrib[alongCoord].SrcPixel[j].Coord; int contribOffset = (horz ? contribCoord : crossCoord) * 4 + (horz ? crossCoord : contribCoord) * bmpSrc.Stride; double weight = contrib[alongCoord].SrcPixel[j].Weight; if (channel != 3) { weight *= srcBytes[contribOffset + 3] / 255d; } if (weight == 0) { continue; } wsum += weight; intensity += srcBytes[contribOffset + channel] * weight; } bmpDest.Data[(horz ? alongCoord : crossCoord) * 4 + (horz ? crossCoord : alongCoord) * bmpDest.Stride + channel] = (byte)Math.Min(Math.Max(intensity / wsum, byte.MinValue), byte.MaxValue); } } } } }
/// <summary> /// Returns a new image which contains a 1 pixel wide black outline of the specified image. /// </summary> public void GetOutline(BitmapBase result, Color color, int threshold, bool inside) { const byte outside = 0; using (UseRead()) using (result.UseWrite()) { var src = Data; var tgt = result.Data; byte cr = color.R, cg = color.G, cb = color.B, ca = color.A; for (int y = 0; y < Height; y++) { int b = y * Stride; int left = outside; int cur = src[b + 0 + 3]; int right; for (int x = 0; x < Width; x++, b += 4) { right = x == Width - 1 ? outside : src[b + 4 + 3]; if ((src[b + 3] <= threshold) ^ inside) { if ( ((left > threshold) ^ inside) || ((right > threshold) ^ inside) || (((y == 0 ? outside : src[b - Stride + 3]) > threshold) ^ inside) || (((y == Height - 1 ? outside : src[b + Stride + 3]) > threshold) ^ inside) ) { tgt[b] = cb; tgt[b + 1] = cg; tgt[b + 2] = cr; tgt[b + 3] = ca; } } left = cur; cur = right; } } } }
/// <summary>A convenience method to save a GDI image directly to a .tga file.</summary> public static unsafe void Save(BitmapBase image, string filename) { using (image.UseRead()) using (var file = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.Read)) { var header = new byte[18]; header[2] = 2; header[12] = (byte) (image.Width); header[13] = (byte) (image.Width >> 8); header[14] = (byte) (image.Height); header[15] = (byte) (image.Height >> 8); header[16] = 32; header[17] = 32; file.Write(header); byte[] dummy = new byte[image.Width * 4]; for (int y = 0; y < image.Height; y++) { Ut.MemCpy(dummy, image.Data + y * image.Stride, image.Width * 4); file.Write(dummy); } } }
/// <summary>A convenience method to save a GDI image directly to a .tga file.</summary> public static unsafe void Save(BitmapBase image, string filename) { using (image.UseRead()) using (var file = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.Read)) { var header = new byte[18]; header[2] = 2; header[12] = (byte)(image.Width); header[13] = (byte)(image.Width >> 8); header[14] = (byte)(image.Height); header[15] = (byte)(image.Height >> 8); header[16] = 32; header[17] = 32; file.Write(header); byte[] dummy = new byte[image.Width * 4]; for (int y = 0; y < image.Height; y++) { Ut.MemCpy(dummy, image.Data + y * image.Stride, image.Width * 4); file.Write(dummy); } } }
public void DrawImage(BitmapBase image, int destX, int destY, int srcX, int srcY, int width, int height, bool below) { using (UseWrite()) using (image.UseRead()) { if (width <= 0 || height <= 0) { return; } if (destX < 0) { srcX -= destX; width += destX; destX = 0; } if (destY < 0) { srcY -= destY; height += destY; destY = 0; } if (srcX >= image.Width || srcY >= image.Height) { return; } if (srcX < 0) { destX -= srcX; width += srcX; srcX = 0; } if (srcY < 0) { destY -= srcY; height += srcY; srcY = 0; } if (destX >= Width || destY >= Height) { return; } if (destX + width > Width) { width = Width - destX; } if (destY + height > Height) { height = Height - destY; } if (srcX + width > image.Width) { width = image.Width - srcX; } if (srcY + height > image.Height) { height = image.Height - srcY; } if (width <= 0 || height <= 0) // cannot be negative at this stage, but just in case... { return; } byte *dest = Data + destY * Stride + destX * 4; byte *src = image.Data + srcY * image.Stride + srcX * 4; for (int y = 0; y < height; y++, dest += Stride, src += image.Stride) { byte *tgt = dest; byte *btm = below ? src : dest; byte *top = below ? dest : src; byte *end = tgt + width * 4; do { byte topA = *(top + 3); byte btmA = *(btm + 3); if (topA == 255 || btmA == 0) { *(int *)tgt = *(int *)top; } else if (topA == 0) { *(int *)tgt = *(int *)btm; } else if (btmA == 255) { // green *(tgt + 1) = (byte)((*(top + 1) * topA + *(btm + 1) * (255 - topA)) >> 8); // red and blue *(uint *)tgt = (*(uint *)tgt & 0xFF00FF00u) | (((((*(uint *)top) & 0x00FF00FFu) * topA + ((*(uint *)btm) & 0x00FF00FFu) * (uint)(255 - topA)) >> 8) & 0x00FF00FFu); // alpha (only needed when "below" is true) *(tgt + 3) = 255; } else // topA and btmA both >0 and <255 { byte tgtAA = *(tgt + 3) = (byte)(topA + (btmA * (255 - topA) >> 8)); int btmAA = (btmA * (255 - topA)) / 255; tgtAA += 1; // ensures the division below never results in a value greater than 255 *(tgt + 0) = (byte)((*(top + 0) * topA + *(btm + 0) * btmAA) / tgtAA); *(tgt + 1) = (byte)((*(top + 1) * topA + *(btm + 1) * btmAA) / tgtAA); *(tgt + 2) = (byte)((*(top + 2) * topA + *(btm + 2) * btmAA) / tgtAA); } tgt += 4; btm += 4; top += 4; }while (tgt < end); } } }
public void DrawImage(BitmapBase image, int destX = 0, int destY = 0, bool below = false) { DrawImage(image, destX, destY, 0, 0, image.Width, image.Height, below); }
public void CopyPixelsFrom(BitmapBase source) { using (source.UseRead()) CopyPixelsFrom(source.Data, source.Width, source.Height, source.Stride); }
public BitmapWriteReleaser(BitmapBase bitmap) { _bmp = bitmap; lock (_bmp) _bmp.acquire(write: true); }
public BitmapReadReleaser(BitmapBase bitmap) { _bmp = bitmap; lock (_bmp) _bmp.acquire(write: false); }
public static unsafe BitmapBase SizePos(BitmapBase source, double scaleWidth, double scaleHeight, int inX, int inY, int outX, int outY, int maxWidth = 0, int maxHeight = 0, Filter filter = null) { if (source.Width <= 0 || source.Height <= 0) { return(source.ToBitmapSame()); } PixelRect pureImg = source.PreciseSize(0); if (pureImg.Width <= 0 || pureImg.Height <= 0) { return(source.ToBitmapSame()); } int outWidth = (int)Math.Round(pureImg.Width * scaleWidth); int outHeight = (int)Math.Round(pureImg.Height * scaleHeight); if (scaleWidth == 1 && scaleHeight == 1) { //no resize needed if (inX != outX || inY != outY) { BitmapBase result; if (maxWidth == 0 && maxHeight == 0) { result = new BitmapRam(outX - inX + source.Width, outY - inY + source.Height); } else { result = new BitmapRam(Math.Min(outX - inX + source.Width, maxWidth), Math.Min(outY - inY + source.Height, maxHeight)); } result.DrawImage(source, outX - inX, outY - inY); return(result); } else { return(source.ToBitmapSame()); } } if (filter == null) { if (scaleWidth < 1) { filter = new LanczosFilter(); } else { filter = new MitchellFilter(); } } int transparentOffset; if (pureImg.Left != 0 || pureImg.Top != 0) { transparentOffset = pureImg.Left * 4 + pureImg.Top * source.Stride; // Resample looks better if transprent pixels is cropped. Especially if the image is square // Data+DataOffset, pureImg.Width, pureImg.Height instead of Data, Width, Height works like left-top cropping } else { transparentOffset = 0; } BitmapBase afterHorzResample, afterVertResample; // Horizontal resampling if (scaleWidth == 1) { afterHorzResample = source; } else { afterHorzResample = new BitmapRam(outWidth, pureImg.Height); ContributorEntry[] contrib = filter.PrecomputeResample(scaleWidth, pureImg.Width, outWidth); Resample1D(afterHorzResample, source, transparentOffset, contrib, outWidth, pureImg.Height, true); transparentOffset = 0; } // Vertical resampling if (scaleHeight == 1) { afterVertResample = afterHorzResample; } else { afterVertResample = new BitmapRam(outWidth, outHeight); ContributorEntry[] contrib = filter.PrecomputeResample(scaleHeight, pureImg.Height, outHeight); Resample1D(afterVertResample, afterHorzResample, transparentOffset, contrib, outHeight, outWidth, false); } BitmapBase final; //At this point image will be resized and moved to another BitmapBase anyway int drawX = outX - (int)Math.Round((inX - pureImg.Left) * scaleWidth); int drawY = outY - (int)Math.Round((inY - pureImg.Top) * scaleHeight); if (maxWidth == 0 && maxHeight == 0) { final = new BitmapRam(Math.Max(drawX + outWidth, maxWidth), Math.Max(drawY + outHeight, maxHeight)); } else { final = new BitmapRam(Math.Max(drawX + outWidth, maxWidth), Math.Max(drawY + outHeight, maxHeight)); } final.DrawImage(afterVertResample, drawX, drawY); return(final); }
internal unsafe void Vertical(BitmapBase src, BitmapBase dest, BlurEdgeMode edgeMode) { for (int x = 0; x < src.Width; x++) { byte* colSource = src.Data + x * 4; byte* colResult = dest.Data + x * 4; for (int y = 0; y < src.Height; y++) { int rSum = 0, gSum = 0, bSum = 0, aSum = 0; for (int k = 0, ySrc = y - _kernel.Length / 2; k < _kernel.Length; k++, ySrc++) { int yRead = ySrc; if (yRead < 0 || yRead >= src.Height) switch (edgeMode) { case BlurEdgeMode.Transparent: continue; case BlurEdgeMode.Same: yRead = yRead < 0 ? 0 : src.Height - 1; break; case BlurEdgeMode.Wrap: yRead = Ut.ModPositive(yRead, src.Height); break; case BlurEdgeMode.Mirror: if (yRead < 0) yRead = -yRead - 1; yRead = yRead % (2 * src.Height); if (yRead >= src.Height) yRead = 2 * src.Height - yRead - 1; break; } yRead *= src.Stride; bSum += _kernel[k] * colSource[yRead + 0]; gSum += _kernel[k] * colSource[yRead + 1]; rSum += _kernel[k] * colSource[yRead + 2]; aSum += _kernel[k] * colSource[yRead + 3]; } int yWrite = y * dest.Stride; colResult[yWrite + 0] = (byte) (bSum / _kernelSum); colResult[yWrite + 1] = (byte) (gSum / _kernelSum); colResult[yWrite + 2] = (byte) (rSum / _kernelSum); colResult[yWrite + 3] = (byte) (aSum / _kernelSum); } } }
/// <summary> /// Applies the effect to the specified layer. Returns the resulting image. If the layer is writable, may modify it /// directly and return the same instance, instead of creating a new one. /// </summary> public abstract BitmapBase Apply(Tank tank, BitmapBase layer);
internal unsafe void Horizontal(BitmapBase src, BitmapBase dest, BlurEdgeMode edgeMode) { for (int y = 0; y < src.Height; y++) { byte* rowSource = src.Data + y * src.Stride; byte* rowResult = dest.Data + y * dest.Stride; for (int x = 0; x < src.Width; x++) { int rSum = 0, gSum = 0, bSum = 0, aSum = 0; for (int k = 0, xSrc = x - _kernel.Length / 2; k < _kernel.Length; k++, xSrc++) { int xRead = xSrc; if (xRead < 0 || xRead >= src.Width) switch (edgeMode) { case BlurEdgeMode.Transparent: continue; case BlurEdgeMode.Same: xRead = xRead < 0 ? 0 : src.Width - 1; break; case BlurEdgeMode.Wrap: xRead = Ut.ModPositive(xRead, src.Width); break; case BlurEdgeMode.Mirror: if (xRead < 0) xRead = -xRead - 1; xRead = xRead % (2 * src.Width); if (xRead >= src.Width) xRead = 2 * src.Width - xRead - 1; break; } xRead <<= 2; // * 4 bSum += _kernel[k] * rowSource[xRead + 0]; gSum += _kernel[k] * rowSource[xRead + 1]; rSum += _kernel[k] * rowSource[xRead + 2]; aSum += _kernel[k] * rowSource[xRead + 3]; } int xWrite = x << 2; // * 4 rowResult[xWrite + 0] = (byte) (bSum / _kernelSum); rowResult[xWrite + 1] = (byte) (gSum / _kernelSum); rowResult[xWrite + 2] = (byte) (rSum / _kernelSum); rowResult[xWrite + 3] = (byte) (aSum / _kernelSum); } } }
public void DrawImage(BitmapBase image, int destX, int destY, int srcX, int srcY, int width, int height, bool below) { using (UseWrite()) using (image.UseRead()) { if (width <= 0 || height <= 0) return; if (destX < 0) { srcX -= destX; width += destX; destX = 0; } if (destY < 0) { srcY -= destY; height += destY; destY = 0; } if (srcX >= image.Width || srcY >= image.Height) return; if (srcX < 0) { destX -= srcX; width += srcX; srcX = 0; } if (srcY < 0) { destY -= srcY; height += srcY; srcY = 0; } if (destX >= Width || destY >= Height) return; if (destX + width > Width) width = Width - destX; if (destY + height > Height) height = Height - destY; if (srcX + width > image.Width) width = image.Width - srcX; if (srcY + height > image.Height) height = image.Height - srcY; if (width <= 0 || height <= 0) // cannot be negative at this stage, but just in case... return; byte* dest = Data + destY * Stride + destX * 4; byte* src = image.Data + srcY * image.Stride + srcX * 4; for (int y = 0; y < height; y++, dest += Stride, src += image.Stride) { byte* tgt = dest; byte* btm = below ? src : dest; byte* top = below ? dest : src; byte* end = tgt + width * 4; do { byte topA = *(top + 3); byte btmA = *(btm + 3); if (topA == 255 || btmA == 0) *(int*) tgt = *(int*) top; else if (topA == 0) *(int*) tgt = *(int*) btm; else if (btmA == 255) { // green *(tgt + 1) = (byte) ((*(top + 1) * topA + *(btm + 1) * (255 - topA)) >> 8); // red and blue *(uint*) tgt = (*(uint*) tgt & 0xFF00FF00u) | (((((*(uint*) top) & 0x00FF00FFu) * topA + ((*(uint*) btm) & 0x00FF00FFu) * (uint) (255 - topA)) >> 8) & 0x00FF00FFu); // alpha (only needed when "below" is true) *(tgt + 3) = 255; } else // topA and btmA both >0 and <255 { byte tgtAA = *(tgt + 3) = (byte) (topA + (btmA * (255 - topA) >> 8)); int btmAA = (btmA * (255 - topA)) / 255; tgtAA += 1; // ensures the division below never results in a value greater than 255 *(tgt + 0) = (byte) ((*(top + 0) * topA + *(btm + 0) * btmAA) / tgtAA); *(tgt + 1) = (byte) ((*(top + 1) * topA + *(btm + 1) * btmAA) / tgtAA); *(tgt + 2) = (byte) ((*(top + 2) * topA + *(btm + 2) * btmAA) / tgtAA); } tgt += 4; btm += 4; top += 4; } while (tgt < end); } } }
/// <summary> /// Applies the effect to the specified layer. Returns the resulting image. The caller must make sure that the layer /// is writable, because all Apply methods expect to be able to modify the layer if necessary and return it, instead of /// creating a new instance. /// </summary> public abstract BitmapBase Apply(RenderTask renderTask, BitmapBase layer);