using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; namespace QuickFont { /// /// A class for basic bitmap manipulation: clearing bitmaps, blitting from one bitmap to another etc. /// /// Generally methods in this class are quite slow and are only intended for pre-rendering. /// /// TODO : replace loops with block copies /// public partial class JBitmap { public enum JBitmapPixelFormat {Format8bpp=1, Format24bppBGR=3, Format32bppBGRA=4 } /// /// Gets the corresponding C# pixel format /// /// /// private static System.Drawing.Imaging.PixelFormat GetPixFormat(JBitmapPixelFormat format) { switch (format){ case JBitmapPixelFormat.Format8bpp: return System.Drawing.Imaging.PixelFormat.Format8bppIndexed; case JBitmapPixelFormat.Format24bppBGR: return System.Drawing.Imaging.PixelFormat.Format24bppRgb; case JBitmapPixelFormat.Format32bppBGRA: return System.Drawing.Imaging.PixelFormat.Format32bppArgb; default: return System.Drawing.Imaging.PixelFormat.Format8bppIndexed; } } public Bitmap bitmap; public BitmapData bitmapData; public JBitmapPixelFormat format; public int h { get { return bitmapData.Height; } } public int w { get { return bitmapData.Width; } } public JBitmap(int w, int h, JBitmapPixelFormat _format) { format = _format; bitmap = new Bitmap(w, h, GetPixFormat(format)); bitmapData = bitmap.LockBits(new Rectangle(0, 0, w, h),ImageLockMode.ReadWrite, GetPixFormat(format)); } public JBitmap(String fileName, JBitmapPixelFormat _format) { format = _format; bitmap = new Bitmap(fileName); bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, GetPixFormat(format)); } public JBitmap CreateCopy() { JBitmap newBitmap = new JBitmap(w, h, format); Blit(newBitmap, w, h); return newBitmap; } public void Clear(byte val) { unsafe { byte* sourcePtr = (byte*)(bitmapData.Scan0); for (int i = 0; i < bitmapData.Height; i++) { for (int j = 0; j < bitmapData.Width; j++) { (*sourcePtr) = val; sourcePtr++; } sourcePtr += bitmapData.Stride - bitmapData.Width * 1; //move to the end of the line (past unused space) } } } public void Clear24(byte r, byte g, byte b) { unsafe { byte* sourcePtr = (byte*)(bitmapData.Scan0); for (int i = 0; i < bitmapData.Height; i++) { for (int j = 0; j < bitmapData.Width; j++) { *(sourcePtr) = b; *(sourcePtr + 1) = g; *(sourcePtr + 2) = r; sourcePtr += 3; } sourcePtr += bitmapData.Stride - bitmapData.Width * 3; //move to the end of the line (past unused space) } } } /// /// Blits this bitmap onto the target - clips regions that are out of bounds. /// /// Only supported for 8bbp /// /// /// /// public void BlitOLD(JBitmap target, int px, int py){ //currently only supported for 8 bpp source / target if (bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed && target.bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed) { int targetStartX, targetEndX; int targetStartY, targetEndY; int copyW, copyH; targetStartX = Math.Max(px,0); targetEndX = Math.Min(px + bitmapData.Width, target.bitmapData.Width); targetStartY = Math.Max(py,0); targetEndY = Math.Min(py + bitmapData.Height, target.bitmapData.Height); copyW = targetEndX - targetStartX; copyH = targetEndY - targetStartY; if(copyW < 0 ){ return; } if(copyH < 0){ return; } int sourceStartX = targetStartX - px; int sourceStartY = targetStartY - py; unsafe { byte* sourcePtr = (byte*)(bitmapData.Scan0); byte* targetPtr = (byte*)(target.bitmapData.Scan0); byte* targetY = targetPtr + targetStartY * target.bitmapData.Stride; byte* sourceY = sourcePtr + sourceStartY * bitmapData.Stride; for (int y = 0; y < copyH; y++, targetY += target.bitmapData.Stride, sourceY += bitmapData.Stride) { byte* targetOffset = targetY + targetStartX; byte* sourceOffset = sourceY + sourceStartX; for (int x = 0; x < copyW; x++, targetOffset++, sourceOffset++) { *(targetOffset) = *(sourceOffset); } } } } } public void Blit(JBitmap target, int srcPx, int srcPy, int srcW, int srcH, int px, int py){ if (target.format != format) { throw new Exception("Attempted to blit between bitmaps not of the same format"); } int bpp = (int)format; int targetStartX, targetEndX; int targetStartY, targetEndY; int copyW, copyH; targetStartX = Math.Max(px, 0); targetEndX = Math.Min(px + srcW, target.bitmapData.Width); targetStartY = Math.Max(py, 0); targetEndY = Math.Min(py + srcH, target.bitmapData.Height); copyW = targetEndX - targetStartX; copyH = targetEndY - targetStartY; if (copyW < 0) { return; } if (copyH < 0) { return; } int sourceStartX = srcPx + targetStartX - px; int sourceStartY = srcPy + targetStartY - py; unsafe { byte* sourcePtr = (byte*)(bitmapData.Scan0); byte* targetPtr = (byte*)(target.bitmapData.Scan0); byte* targetY = targetPtr + targetStartY * target.bitmapData.Stride; byte* sourceY = sourcePtr + sourceStartY * bitmapData.Stride; for (int y = 0; y < copyH; y++, targetY += target.bitmapData.Stride, sourceY += bitmapData.Stride) { byte* targetOffset = targetY + targetStartX * bpp; byte* sourceOffset = sourceY + sourceStartX * bpp; for (int x = 0; x < copyW * bpp; x++, targetOffset++, sourceOffset++) { *(targetOffset) = *(sourceOffset); } } } } public void Blit(JBitmap target, int px, int py) { if (target.format != format) { throw new Exception("Attempted to blit between bitmaps not of the same format"); } int bpp = (int)format; int targetStartX, targetEndX; int targetStartY, targetEndY; int copyW, copyH; targetStartX = Math.Max(px, 0); targetEndX = Math.Min(px + bitmapData.Width, target.bitmapData.Width); targetStartY = Math.Max(py, 0); targetEndY = Math.Min(py + bitmapData.Height, target.bitmapData.Height); copyW = targetEndX - targetStartX; copyH = targetEndY - targetStartY; if (copyW < 0) { return; } if (copyH < 0) { return; } int sourceStartX = targetStartX - px; int sourceStartY = targetStartY - py; unsafe { byte* sourcePtr = (byte*)(bitmapData.Scan0); byte* targetPtr = (byte*)(target.bitmapData.Scan0); byte* targetY = targetPtr + targetStartY * target.bitmapData.Stride; byte* sourceY = sourcePtr + sourceStartY * bitmapData.Stride; for (int y = 0; y < copyH; y++, targetY += target.bitmapData.Stride, sourceY += bitmapData.Stride) { byte* targetOffset = targetY + targetStartX*bpp; byte* sourceOffset = sourceY + sourceStartX*bpp; for (int x = 0; x < copyW*bpp; x++, targetOffset++, sourceOffset++) { *(targetOffset) = *(sourceOffset); } } } } /// /// Additively blits onto the target. /// /// Only supported for 8bbp. /// /// /// /// public void BlitAdditive(JBitmap target, int px, int py, float strength) { //currently only supported for 8 bpp source / target if (bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed && target.bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed) { int targetStartX, targetEndX; int targetStartY, targetEndY; int copyW, copyH; targetStartX = Math.Max(px, 0); targetEndX = Math.Min(px + bitmapData.Width, target.bitmapData.Width); targetStartY = Math.Max(py, 0); targetEndY = Math.Min(py + bitmapData.Height, target.bitmapData.Height); copyW = targetEndX - targetStartX; copyH = targetEndY - targetStartY; if (copyW < 0) { return; } if (copyH < 0) { return; } int sourceStartX = targetStartX - px; int sourceStartY = targetStartY - py; unsafe { byte* sourcePtr = (byte*)(bitmapData.Scan0); byte* targetPtr = (byte*)(target.bitmapData.Scan0); int sum; byte* targetY = targetPtr + targetStartY * target.bitmapData.Stride; byte* sourceY = sourcePtr + sourceStartY * bitmapData.Stride; for (int y = 0; y < copyH; y++, targetY += target.bitmapData.Stride, sourceY += bitmapData.Stride) { byte* targetOffset = targetY + targetStartX; byte* sourceOffset = sourceY + sourceStartX; for (int x = 0; x < copyW; x++, targetOffset++, sourceOffset++) { sum = (int)*(targetOffset) + (int) (*(sourceOffset) * strength); if (sum > 255) { *(targetOffset) = 255; } else { *(targetOffset) = (byte)sum; } } } } } } /// /// Additively blits onto the target. /// /// Supports just 24bpp (source and target) /// /// /// /// public void BlitAdditive24(JBitmap target, int px, int py, float strength) { //currently only supported for 8 bpp source / target if (bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb && target.bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb) { int targetStartX, targetEndX; int targetStartY, targetEndY; int copyW, copyH; targetStartX = Math.Max(px, 0); targetEndX = Math.Min(px + bitmapData.Width, target.bitmapData.Width); targetStartY = Math.Max(py, 0); targetEndY = Math.Min(py + bitmapData.Height, target.bitmapData.Height); copyW = targetEndX - targetStartX; copyH = targetEndY - targetStartY; if (copyW < 0) { return; } if (copyH < 0) { return; } int sourceStartX = targetStartX - px; int sourceStartY = targetStartY - py; unsafe { byte* sourcePtr = (byte*)(bitmapData.Scan0); byte* targetPtr = (byte*)(target.bitmapData.Scan0); int sum; byte* targetY = targetPtr + targetStartY * target.bitmapData.Stride; byte* sourceY = sourcePtr + sourceStartY * bitmapData.Stride; for (int y = 0; y < copyH; y++, targetY += target.bitmapData.Stride, sourceY += bitmapData.Stride) { byte* targetOffset = targetY + targetStartX*3; byte* sourceOffset = sourceY + sourceStartX*3; for (int x = 0; x < copyW*3; x++, targetOffset++, sourceOffset++) { sum = (int)*(targetOffset) + (int)(*(sourceOffset) * strength); if (sum > 255) { *(targetOffset) = 255; } else { *(targetOffset) = (byte)sum; } } } } } } public void BlitMax(JBitmap target, int px, int py, float strength) { //currently only supported for 8 bpp source / target if (bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed && target.bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed) { int targetStartX, targetEndX; int targetStartY, targetEndY; int copyW, copyH; targetStartX = Math.Max(px, 0); targetEndX = Math.Min(px + bitmapData.Width, target.bitmapData.Width); targetStartY = Math.Max(py, 0); targetEndY = Math.Min(py + bitmapData.Height, target.bitmapData.Height); copyW = targetEndX - targetStartX; copyH = targetEndY - targetStartY; if (copyW < 0) { return; } if (copyH < 0) { return; } int sourceStartX = targetStartX - px; int sourceStartY = targetStartY - py; unsafe { byte* sourcePtr = (byte*)(bitmapData.Scan0); byte* targetPtr = (byte*)(target.bitmapData.Scan0); int max; byte* targetY = targetPtr + targetStartY * target.bitmapData.Stride; byte* sourceY = sourcePtr + sourceStartY * bitmapData.Stride; for (int y = 0; y < copyH; y++, targetY += target.bitmapData.Stride, sourceY += bitmapData.Stride) { byte* targetOffset = targetY + targetStartX; byte* sourceOffset = sourceY + sourceStartX; for (int x = 0; x < copyW; x++, targetOffset++, sourceOffset++) { max = Math.Max((int)*(targetOffset), (int) (*(sourceOffset) * strength)); if (*(sourceOffset) * strength > *(targetOffset)) { max = (int) (*(sourceOffset) * strength ); if (max > 255) { *(targetOffset) = 255; } else { *(targetOffset) = (byte)max; } } } } } } } /// /// Just unlocks the bits, so that the bitmap can no longer be blitted to / from. /// public void Free(){ bitmap.UnlockBits(bitmapData); bitmap.Dispose(); } /* * Resizes this bitmap to a smaller size using area-weighted averaging. * */ public void DownScale(int newWidth, int newHeight) { JBitmap newBitmap = new JBitmap(newWidth, newHeight, format); float xscale = (float)bitmapData.Width / newWidth; float yscale = (float)bitmapData.Height / newHeight; byte r = 0, g = 0, b = 0, a = 0; float summedR = 0f; float summedG = 0f; float summedB = 0f; float summedA = 0f; int left, right, top, bottom; //the area of old pixels covered by the new bitmap float targetStartX, targetEndX; float targetStartY, targetEndY; float leftF, rightF, topF, bottomF; //edges of new pixel in old pixel coords float weight; float weightScale = xscale * yscale; float weightTotalTest = 0f; for (int m = 0; m < newHeight; m++) { for (int n = 0; n < newWidth; n++) { leftF = n * xscale; rightF = (n + 1) * xscale; topF = m * yscale; bottomF = (m + 1) * yscale; left = (int)leftF; right = (int)rightF; top = (int)topF; bottom = (int)bottomF; if (left < 0) left = 0; if (top < 0) top = 0; if (right >= bitmapData.Width) right = bitmapData.Width - 1; if (bottom >= bitmapData.Height) bottom = bitmapData.Height - 1; summedR = 0f; summedG = 0f; summedB = 0f; summedA = 0f; weightTotalTest = 0f; for (int j = top; j <= bottom; j++) { for (int i = left; i <= right; i++) { targetStartX = Math.Max(leftF, i); targetEndX = Math.Min(rightF, i + 1); targetStartY = Math.Max(topF, j); targetEndY = Math.Min(bottomF, j + 1); weight = (targetEndX - targetStartX) * (targetEndY - targetStartY); GetPixelFast(i, j, ref r, ref g, ref b, ref a); summedR += weight * r; summedG += weight * g; summedB += weight * b; summedA += weight * a; weightTotalTest += weight; } } summedR /= weightScale; summedG /= weightScale; summedB /= weightScale; summedA /= weightScale; if (summedR < 0) summedR = 0f; if (summedG < 0) summedG = 0f; if (summedB < 0) summedB = 0f; if (summedA < 0) summedA = 0f; if (summedR >= 256) summedR = 255; if (summedG >= 256) summedG = 255; if (summedB >= 256) summedB = 255; if (summedA >= 256) summedA = 255; newBitmap.PutPixelFast(n, m, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); } } this.Free(); this.bitmap = newBitmap.bitmap; this.bitmapData = newBitmap.bitmapData; this.format = newBitmap.format; } public byte GetPixel(int px, int py) { py = bitmapData.Height - py; if (px >= 0 && py >= 0 && px < bitmapData.Width && py < bitmapData.Height) { unsafe { byte* addr = (byte*)(bitmapData.Scan0); addr += bitmapData.Stride * py + px; return *addr; } } else { return 0; } } public void GetPixel24(int px, int py, out byte r, out byte g, out byte b) { py = bitmapData.Height - py; if (px >= 0 && py >= 0 && px < bitmapData.Width && py < bitmapData.Height) { unsafe { byte* addr = (byte*)(bitmapData.Scan0); addr += bitmapData.Stride * py + px * 3; b = *addr; g = *(addr + 1); r = *(addr + 2); } } else { r = 0; g = 0; b = 0; } } public void GetPixelFast(int px, int py, ref byte r, ref byte g, ref byte b, ref byte a) { unsafe { byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * (int)format; if (format == JBitmapPixelFormat.Format8bpp) { r = *addr; } else if (format == JBitmapPixelFormat.Format24bppBGR) { b = *addr; g = *(addr + 1); r = *(addr + 2); } else if (format == JBitmapPixelFormat.Format32bppBGRA) { b = *addr; g = *(addr + 1); r = *(addr + 2); a = *(addr + 3); } } } public void GetPixelFast(int px, int py, ref byte r, ref byte g, ref byte b) { unsafe { byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * (int)format; if (format == JBitmapPixelFormat.Format8bpp) { r = *addr; } else if (format == JBitmapPixelFormat.Format24bppBGR) { b = *addr; g = *(addr + 1); r = *(addr + 2); } else if (format == JBitmapPixelFormat.Format32bppBGRA) { b = *addr; g = *(addr + 1); r = *(addr + 2); } } } /* * For 24-bit - just returns blue channel * For 32-bit - returns alpha channel */ public void GetPixelFast(int px, int py, ref byte col) { unsafe { byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * (int)format; if (format == JBitmapPixelFormat.Format8bpp) { col = *addr; } else if (format == JBitmapPixelFormat.Format24bppBGR) { col = *addr; } else if (format == JBitmapPixelFormat.Format32bppBGRA) { col = *(addr + 3); } } } public void PutPixelFast(int px, int py, byte r, byte g, byte b, byte a) { unsafe { byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * (int)format; if (format == JBitmapPixelFormat.Format8bpp) { *addr = r; } else if (format == JBitmapPixelFormat.Format24bppBGR) { *addr = b; *(addr + 1) = g; *(addr + 2) = r; } else if (format == JBitmapPixelFormat.Format32bppBGRA) { *addr = b; *(addr + 1) = g; *(addr + 2) = r; *(addr + 3) = a; } } } public void PutPixelFast(int px, int py, byte r, byte g, byte b) { unsafe { byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * (int)format; if (format == JBitmapPixelFormat.Format8bpp) { *addr = r; } else if (format == JBitmapPixelFormat.Format24bppBGR) { *addr = b; *(addr + 1) = g; *(addr + 2) = r; } else if (format == JBitmapPixelFormat.Format32bppBGRA) { *addr = b; *(addr + 1) = g; *(addr + 2) = r; } } } /* * For 24-bit - just sets blue channel * For 32-bit - sets alpha channel */ public void PutPixelFast(int px, int py, byte col) { unsafe { byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * (int)format; if (format == JBitmapPixelFormat.Format8bpp) { *addr = col; } else if (format == JBitmapPixelFormat.Format24bppBGR) { *addr = col; } else if (format == JBitmapPixelFormat.Format32bppBGRA) { *(addr + 3) = col; } } } } public class JBitmapManager { List bitmapList; public JBitmapManager() { bitmapList = new List(); } public JBitmap Add(JBitmap bmp) { bitmapList.Add(bmp); return bmp; } public void Remove(JBitmap bmpToRemove) { bitmapList.Remove(bmpToRemove); bmpToRemove.Free(); } public void FreeAll() { foreach (JBitmap bmp in bitmapList) { bmp.Free(); } } } }