public static unsafe Bitmap GetImageFromMask(int width, int height, ReadOnlySpan <byte> mask) { Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb); var data = bmp.BasicLockBits(ImageLockMode.WriteOnly); uint *ptrInt = (uint *)data.Scan0; int bmpStride = data.Stride / 4; int stride = ((width + 7) / 8) * 8; FastBitArray arr = new FastBitArray(mask); int strideFactor = 0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (arr.GetReverse(x + strideFactor)) { *(ptrInt + x + (y * bmpStride)) = 0xFFFFFFFFu; } else { *(ptrInt + x + (y * bmpStride)) = 0xFF000000u; } } strideFactor += stride; } bmp.UnlockBits(data); return(bmp); }
public static AssetSprite.CollisionMaskInfo GetInfoForSprite(ProjectFile pf, GMSprite spr, out List <Bitmap> bitmaps, bool suggestPrecise = false) { bitmaps = new List <Bitmap>(spr.TextureItems.Count); var info = new AssetSprite.CollisionMaskInfo { Mode = (MaskMode)spr.BBoxMode }; if (spr.SepMasks == GMSprite.SepMaskType.AxisAlignedRect) { info.Type = MaskType.Rectangle; } else if (spr.SepMasks == GMSprite.SepMaskType.RotatedRect) { info.Type = MaskType.RectangleWithRotation; } // Some basic conditions to bail if (spr.CollisionMasks.Count != 1 && spr.CollisionMasks.Count != spr.TextureItems.Count) { return(info); } if (spr.CollisionMasks.Count == 0) { return(info); } // Get bitmaps from frames bitmaps = GetBitmaps(pf, spr.Width, spr.Height, spr.TextureItems); List <BitmapData> bitmapData = new List <BitmapData>(bitmaps.Count); foreach (var item in bitmaps) { bitmapData.Add(item.BasicLockBits()); } int boundLeft = Math.Clamp(spr.MarginLeft, 0, spr.Width), boundRight = Math.Clamp(spr.MarginRight, 0, spr.Width - 1), boundTop = Math.Clamp(spr.MarginTop, 0, spr.Height), boundBottom = Math.Clamp(spr.MarginBottom, 0, spr.Height - 1); switch (spr.SepMasks) { case GMSprite.SepMaskType.AxisAlignedRect: case GMSprite.SepMaskType.RotatedRect: switch (info.Mode) { case MaskMode.Automatic: // Scan for the lowest alpha value in the bounding box // When comparing each pixel, compare to the one in that spot with the highest alpha in every frame bool foundNonzero = false; byte lowest = 0; byte highest = 0; int stride = ((spr.Width + 7) / 8) * 8; FastBitArray mask = new FastBitArray(spr.CollisionMasks[0].Memory.Span); int strideFactor = boundTop * stride; for (int y = boundTop; y <= boundBottom; y++) { for (int x = boundLeft; x <= boundRight; x++) { if (mask.GetReverse(x + strideFactor)) { byte highestAlpha = GetHighestAlphaAt(bitmapData, x, y); if (highestAlpha > highest) { highest = highestAlpha; } if (highestAlpha != 0 && (!foundNonzero || highestAlpha < lowest)) { lowest = highestAlpha; foundNonzero = true; } } } strideFactor += stride; } if (foundNonzero) { if (lowest == highest) { lowest = 0; // Could be anything } else { --lowest; } } info.AlphaTolerance = lowest; break; case MaskMode.Manual: info.Left = spr.MarginLeft; info.Right = spr.MarginRight; info.Top = spr.MarginTop; info.Bottom = spr.MarginBottom; break; } break; case GMSprite.SepMaskType.Precise: { int stride = ((spr.Width + 7) / 8) * 8; bool foundNonzero = false; byte lowest = 0; byte highest = 0; if (spr.CollisionMasks.Count > 1 && spr.CollisionMasks.Count == spr.TextureItems.Count) { info.Type = MaskType.PrecisePerFrame; unsafe { for (int i = 0; i < spr.CollisionMasks.Count; i++) { BitmapData item = bitmapData[i]; FastBitArray mask = new FastBitArray(spr.CollisionMasks[i].Memory.Span); int strideFactor = boundTop * stride; for (int y = boundTop; y <= boundBottom; y++) { for (int x = boundLeft; x <= boundRight; x++) { if (mask.GetReverse(x + strideFactor)) { byte val = *((byte *)item.Scan0 + (x * 4) + (y * item.Stride) + 3); if (val > highest) { highest = val; } if (val != 0 && (!foundNonzero || val < lowest)) { lowest = val; foundNonzero = true; } } } strideFactor += stride; } } } } else { info.Type = MaskType.Precise; // Scan for highest alpha, as well as diamond/ellipses FastBitArray mask = new FastBitArray(spr.CollisionMasks[0].Memory.Span); bool isDiamond = true, isEllipse = true; float centerX = ((spr.MarginLeft + spr.MarginRight) / 2); float centerY = ((spr.MarginTop + spr.MarginBottom) / 2); float radiusX = centerX - spr.MarginLeft + 0.5f; float radiusY = centerY - spr.MarginTop + 0.5f; int strideFactor = boundTop * stride; if (!suggestPrecise && radiusX > 0f && radiusY > 0f) { for (int y = boundTop; y <= boundBottom; y++) { for (int x = boundLeft; x <= boundRight; x++) { float normalX = (x - centerX) / radiusX; float normalY = (y - centerY) / radiusY; bool inDiamond = Math.Abs(normalX) + Math.Abs(normalY) <= 1f; bool inEllipse = Math.Pow(normalX, 2.0d) + Math.Pow(normalY, 2.0d) <= 1.0d; if (mask.GetReverse(x + strideFactor)) { isDiamond &= inDiamond; isEllipse &= inEllipse; byte highestAlpha = GetHighestAlphaAt(bitmapData, x, y); if (highestAlpha > highest) { highest = highestAlpha; } if (highestAlpha != 0 && (!foundNonzero || highestAlpha < lowest)) { lowest = highestAlpha; foundNonzero = true; } } // Can't eliminate based on this, they can be split into pieces with multiple frames //else //{ // isDiamond &= !inDiamond; // isEllipse &= !inEllipse; //} } strideFactor += stride; } } else { // Version without diamond/ellipse checks isDiamond = false; isEllipse = false; for (int y = boundTop; y <= boundBottom; y++) { for (int x = boundLeft; x <= boundRight; x++) { if (mask.GetReverse(x + strideFactor)) { byte highestAlpha = GetHighestAlphaAt(bitmapData, x, y); if (highestAlpha > highest) { highest = highestAlpha; } if (highestAlpha != 0 && (!foundNonzero || highestAlpha < lowest)) { lowest = highestAlpha; foundNonzero = true; } } } strideFactor += stride; } } if (isDiamond) { info.Type = MaskType.Diamond; } else if (isEllipse) { info.Type = MaskType.Ellipse; } } if (info.Mode == MaskMode.Manual || (info.Mode == MaskMode.Automatic && info.Type != MaskType.Precise && info.Type != MaskType.PrecisePerFrame)) { info.Left = spr.MarginLeft; info.Right = spr.MarginRight; info.Top = spr.MarginTop; info.Bottom = spr.MarginBottom; } if (info.Mode == MaskMode.Automatic || info.Type == MaskType.Precise || (info.Mode == MaskMode.Manual && info.Type == MaskType.PrecisePerFrame)) { if (foundNonzero) { if (lowest == highest) { lowest = 0; // Could be anything } else { --lowest; } } info.AlphaTolerance = lowest; } } break; } for (int i = 0; i < bitmaps.Count; i++) { bitmaps[i].UnlockBits(bitmapData[i]); } return(info); }