void ISerializeExplicit.ReadData(IDataReader reader) { int version; try { reader.ReadValue("version", out version); } catch (Exception) { version = Serialize_Version_Unknown; } string formatId; if (version == Serialize_Version_FormatId) { reader.ReadValue("formatId", out formatId); } else if (version == Serialize_Version_LayerPng) { formatId = ImageCodec.FormatPng; } else { throw new NotSupportedException(string.Format( "Unknown PixelData serialization version '{0}'. Can't load image data.", version)); } IImageCodec codec = ImageCodec.GetRead(formatId); if (codec == null) { throw new NotSupportedException(string.Format( "Unable to retrieve image codec for format '{0}'. Can't load image data.", formatId)); } byte[] dataBlock; reader.ReadValue("pixelData", out dataBlock); using (MemoryStream stream = new MemoryStream(dataBlock)) { PixelData pixelData = codec.Read(stream); this.data = pixelData.data; this.width = pixelData.width; this.height = pixelData.height; pixelData = null; } }
/// <summary> /// Renders a text to the specified target Image. /// </summary> /// <param name="text"></param> /// <param name="target"></param> public void RenderToBitmap(string text, PixelData target, float x = 0.0f, float y = 0.0f, PixelData icons = null) { // Rendering int fontNum = this.fonts != null ? this.fonts.Length : 0; RenderState state = new RenderState(this); Element elem; while ((elem = state.NextElement()) != null) { if (elem is TextElement && state.Font != null) { TextElement textElem = elem as TextElement; state.Font.RenderToBitmap( state.CurrentElemText, target, x + state.CurrentElemOffset.X, y + state.CurrentElemOffset.Y + state.LineBaseLine - state.Font.BaseLine, state.Color); } else if (elem is IconElement) { IconElement iconElem = elem as IconElement; Icon icon = iconElem.IconIndex >= 0 && iconElem.IconIndex < this.icons.Length ? this.icons[iconElem.IconIndex] : new Icon(); Vector2 iconSize = icon.size; Vector2 iconOffset = icon.offset; Rect iconUvRect = icon.uvRect; Vector2 dataCoord = iconUvRect.Pos * new Vector2(icons.Width, icons.Height); Vector2 dataSize = iconUvRect.Size * new Vector2(icons.Width, icons.Height); PixelData iconLayer = icons.CloneSubImage( MathF.RoundToInt(dataCoord.X), MathF.RoundToInt(dataCoord.Y), MathF.RoundToInt(dataSize.X), MathF.RoundToInt(dataSize.Y)); iconLayer.Rescale( MathF.RoundToInt(iconSize.X), MathF.RoundToInt(iconSize.Y)); iconLayer.DrawOnto(target, BlendMode.Alpha, MathF.RoundToInt(x + state.CurrentElemOffset.X + iconOffset.X), MathF.RoundToInt(y + state.CurrentElemOffset.Y + state.LineBaseLine - iconSize.Y + iconOffset.Y), iconLayer.Width, iconLayer.Height, 0, 0, state.Color); } } }
/// <summary> /// Generates pixel and atlas data for a single <see cref="Tileset"/> visual layer. /// </summary> /// <param name="renderInput"></param> /// <param name="sourceData"></param> /// <param name="geometry"></param> /// <param name="tileData"></param> /// <returns></returns> private LayerPixelData GenerateLayerPixelData(TilesetRenderInput renderInput, PixelData sourceData, LayerGeometry geometry, RawList<TileInfo> tileData) { // Create a buffer for writing target pixel data LayerPixelData target; target.PixelData = new PixelData(geometry.TargetTextureSize.X, geometry.TargetTextureSize.Y); target.Atlas = new List<Rect>(); // Iterate over tiles and move each tile from source to target Point2 targetTilePos = new Point2(0, 0); for (int tileIndex = 0; tileIndex < geometry.SourceTileCount; tileIndex++) { // Initialize a new tile info when necessary if (tileIndex >= tileData.Count) { tileData.Count++; tileData.Data[tileIndex].IsVisuallyEmpty = true; } // Determine where on the source buffer the tile is located Point2 sourceTilePos = new Point2( geometry.SourceTileAdvance.X * (tileIndex % geometry.SourceTilesPerRow), geometry.SourceTileAdvance.Y * (tileIndex / geometry.SourceTilesPerRow)); // Draw the source tile onto the target buffer, including its spacing / border Point2 targetContentPos = new Point2( targetTilePos.X + renderInput.TargetTileMargin, targetTilePos.Y + renderInput.TargetTileMargin); sourceData.DrawOnto(target.PixelData, BlendMode.Solid, targetContentPos.X, targetContentPos.Y, renderInput.SourceTileSize.X, renderInput.SourceTileSize.Y, sourceTilePos.X, sourceTilePos.Y); // Fill up the target spacing area with similar pixels if (renderInput.TargetTileMargin > 0) { FillTileSpacing(target.PixelData, renderInput.TargetTileMargin, targetContentPos, renderInput.SourceTileSize); } // Update whether the tile is considered visually empty if (tileData.Data[tileIndex].IsVisuallyEmpty) { bool isLayerVisuallyEmpty = IsCompletelyTransparent( sourceData, sourceTilePos, renderInput.SourceTileSize); if (!isLayerVisuallyEmpty) tileData.Data[tileIndex].IsVisuallyEmpty = false; } // Add an entry to the generated atlas Rect atlasRect = new Rect( targetTilePos.X + renderInput.TargetTileMargin, targetTilePos.Y + renderInput.TargetTileMargin, geometry.TargetTileAdvance.X - renderInput.TargetTileMargin * 2, geometry.TargetTileAdvance.Y - renderInput.TargetTileMargin * 2); target.Atlas.Add(atlasRect); // Advance the target tile position targetTilePos.X += geometry.TargetTileAdvance.X; if (targetTilePos.X + geometry.TargetTileAdvance.X > target.PixelData.Width) { targetTilePos.X = 0; targetTilePos.Y += geometry.TargetTileAdvance.Y; } } return target; }
/// <summary> /// Determines the overall geometry of a single <see cref="Tileset"/> visual layer. This involves /// tile boundaries in source and target data, as well as texture sizes and similar. /// </summary> /// <param name="renderInput"></param> /// <param name="layerData"></param> /// <returns></returns> private LayerGeometry CalculateLayerGeometry(TilesetRenderInput renderInput, PixelData layerData) { LayerGeometry geometry; // What's the space requirement for each tile? geometry.SourceTileAdvance = renderInput.SourceTileAdvance; geometry.TargetTileAdvance = renderInput.TargetTileAdvance; // How many tiles will we have? Point2 tileCount = renderInput.GetSourceTileCount(layerData.Width, layerData.Height); geometry.SourceTilesPerRow = tileCount.X; geometry.SourceTilesPerColumn = tileCount.Y; geometry.SourceTileCount = geometry.SourceTilesPerRow * geometry.SourceTilesPerColumn; geometry.TargetTileCount = geometry.SourceTileCount; // ToDo: Account for expanded AutoTiles // What's the optimal texture size to include them all? int minTilesPerLine = MathF.Max(1, (int)MathF.Sqrt(geometry.TargetTileCount)); geometry.TargetTextureSize.X = MathF.NextPowerOfTwo(geometry.TargetTileAdvance.X * minTilesPerLine); int actualTilesPerLine = geometry.TargetTextureSize.X / geometry.TargetTileAdvance.X; int requiredLineCount = 1 + (geometry.TargetTileCount / actualTilesPerLine); geometry.TargetTextureSize.Y = MathF.NextPowerOfTwo(geometry.TargetTileAdvance.Y * requiredLineCount); return geometry; }
/// <summary> /// Determines whether the specified pixel data block is completely transparent in /// the specified area. /// </summary> /// <param name="data"></param> /// <param name="pos"></param> /// <param name="size"></param> /// <returns></returns> private static bool IsCompletelyTransparent(PixelData data, Point2 pos, Point2 size) { for (int y = pos.Y; y < pos.Y + size.Y; y++) { for (int x = pos.X; x < pos.X + size.X; x++) { if (data[x, y].A > 0) return false; } } return true; }
/// <summary> /// Fills up the specified spacing around a tile's pixel data with colors that are /// similar to the existing edge colors in order to prevent filtering artifacts /// when rendering them as a texture atlas. /// </summary> /// <param name="targetData"></param> /// <param name="targetTileSpacing"></param> /// <param name="targetContentPos"></param> /// <param name="targetTileSize"></param> private static void FillTileSpacing(PixelData targetData, int targetTileSpacing, Point2 targetContentPos, Point2 targetTileSize) { ColorRgba[] rawData = targetData.Data; int width = targetData.Width; int baseIndex; int offsetIndex; // Top for (int offset = 1; offset <= targetTileSpacing; offset++) { baseIndex = targetContentPos.Y * width + targetContentPos.X; offsetIndex = (targetContentPos.Y - offset) * width + targetContentPos.X; for (int i = 0; i < targetTileSize.X; i++) { rawData[offsetIndex + i] = rawData[baseIndex + i]; } } // Bottom for (int offset = 1; offset <= targetTileSpacing; offset++) { baseIndex = (targetContentPos.Y + targetTileSize.Y - 1) * width + targetContentPos.X; offsetIndex = (targetContentPos.Y + targetTileSize.Y - 1 + offset) * width + targetContentPos.X; for (int i = 0; i < targetTileSize.X; i++) { rawData[offsetIndex + i] = rawData[baseIndex + i]; } } // Left for (int offset = 1; offset <= targetTileSpacing; offset++) { baseIndex = targetContentPos.Y * width + targetContentPos.X; offsetIndex = targetContentPos.Y * width + targetContentPos.X - offset; for (int i = 0; i < targetTileSize.X; i++) { rawData[offsetIndex + i * width] = rawData[baseIndex + i * width]; } } // Right for (int offset = 1; offset <= targetTileSpacing; offset++) { baseIndex = targetContentPos.Y * width + targetContentPos.X + targetTileSize.X - 1; offsetIndex = targetContentPos.Y * width + targetContentPos.X + targetTileSize.X - 1 + offset; for (int i = 0; i < targetTileSize.X; i++) { rawData[offsetIndex + i * width] = rawData[baseIndex + i * width]; } } // Top Left Corner baseIndex = targetContentPos.Y * width + targetContentPos.X; for (int offsetY = 1; offsetY <= targetTileSpacing; offsetY++) { for (int offsetX = 1; offsetX <= targetTileSpacing; offsetX++) { offsetIndex = baseIndex - offsetX - offsetY * width; rawData[offsetIndex] = rawData[baseIndex]; } } // Top Right Corner baseIndex = targetContentPos.Y * width + targetContentPos.X + targetTileSize.X - 1; for (int offsetY = 1; offsetY <= targetTileSpacing; offsetY++) { for (int offsetX = 1; offsetX <= targetTileSpacing; offsetX++) { offsetIndex = baseIndex + offsetX - offsetY * width; rawData[offsetIndex] = rawData[baseIndex]; } } // Bottom Left Corner baseIndex = (targetContentPos.Y + targetTileSize.Y - 1) * width + targetContentPos.X; for (int offsetY = 1; offsetY <= targetTileSpacing; offsetY++) { for (int offsetX = 1; offsetX <= targetTileSpacing; offsetX++) { offsetIndex = baseIndex - offsetX + offsetY * width; rawData[offsetIndex] = rawData[baseIndex]; } } // Bottom Right Corner baseIndex = (targetContentPos.Y + targetTileSize.Y - 1) * width + targetContentPos.X + targetTileSize.X - 1; for (int offsetY = 1; offsetY <= targetTileSpacing; offsetY++) { for (int offsetX = 1; offsetX <= targetTileSpacing; offsetX++) { offsetIndex = baseIndex + offsetX + offsetY * width; rawData[offsetIndex] = rawData[baseIndex]; } } }
/// <summary> /// Performs a drawing operation from this Layer to a target layer. /// </summary> /// <param name="target"></param> /// <param name="blend"></param> /// <param name="destX"></param> /// <param name="destY"></param> /// <param name="width"></param> /// <param name="height"></param> /// <param name="srcX"></param> /// <param name="srcY"></param> public void DrawOnto(PixelData target, BlendMode blend, int destX, int destY, int width = -1, int height = -1, int srcX = 0, int srcY = 0) { if (width == -1) { width = this.width; } if (height == -1) { height = this.height; } int beginX = MathF.Max(0, -destX, -srcX); int beginY = MathF.Max(0, -destY, -srcY); int endX = MathF.Min(width, this.width, target.width - destX, this.width - srcX); int endY = MathF.Min(height, this.height, target.height - destY, this.height - srcY); if (endX - beginX < 1) { return; } if (endY - beginY < 1) { return; } Parallel.ForEach(Partitioner.Create(beginX, endX), range => { for (int i = range.Item1; i < range.Item2; i++) { for (int j = beginY; j < endY; j++) { int sourceN = srcX + i + this.width * (srcY + j); int targetN = destX + i + target.width * (destY + j); if (blend == BlendMode.Solid) { target.data[targetN] = this.data[sourceN]; } else if (blend == BlendMode.Mask) { // ToDo: it was >= 0? really? if (this.data[sourceN].A > 0) { target.data[targetN] = this.data[sourceN]; } } else if (blend == BlendMode.Add) { ColorRgba targetColor = target.data[targetN]; float alphaTemp = (float)this.data[sourceN].A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(targetColor.R + this.data[sourceN].R * alphaTemp))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(targetColor.G + this.data[sourceN].G * alphaTemp))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(targetColor.B + this.data[sourceN].B * alphaTemp))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)targetColor.A + (int)this.data[sourceN].A)); } else if (blend == BlendMode.Alpha) { ColorRgba targetColor = target.data[targetN]; float alphaTemp = (float)this.data[sourceN].A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(targetColor.R * (1.0f - alphaTemp) + this.data[sourceN].R * alphaTemp))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(targetColor.G * (1.0f - alphaTemp) + this.data[sourceN].G * alphaTemp))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(targetColor.B * (1.0f - alphaTemp) + this.data[sourceN].B * alphaTemp))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(targetColor.A * (1.0f - alphaTemp) + this.data[sourceN].A))); } else if (blend == BlendMode.AlphaPre) { ColorRgba targetColor = target.data[targetN]; float alphaTemp = (float)this.data[sourceN].A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(targetColor.R * (1.0f - alphaTemp) + this.data[sourceN].R))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(targetColor.G * (1.0f - alphaTemp) + this.data[sourceN].G))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(targetColor.B * (1.0f - alphaTemp) + this.data[sourceN].B))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(targetColor.A * (1.0f - alphaTemp) + this.data[sourceN].A))); } else if (blend == BlendMode.Multiply) { ColorRgba targetColor = target.data[targetN]; float clrTempR = (float)targetColor.R / 255.0f; float clrTempG = (float)targetColor.G / 255.0f; float clrTempB = (float)targetColor.B / 255.0f; float clrTempA = (float)targetColor.A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(this.data[sourceN].R * clrTempR))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(this.data[sourceN].G * clrTempG))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(this.data[sourceN].B * clrTempB))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)targetColor.A + (int)this.data[sourceN].A)); } else if (blend == BlendMode.Light) { ColorRgba targetColor = target.data[targetN]; float clrTempR = (float)targetColor.R / 255.0f; float clrTempG = (float)targetColor.G / 255.0f; float clrTempB = (float)targetColor.B / 255.0f; float clrTempA = (float)targetColor.A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(this.data[sourceN].R * clrTempR + targetColor.R))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(this.data[sourceN].G * clrTempG + targetColor.G))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(this.data[sourceN].B * clrTempB + targetColor.B))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)targetColor.A + (int)this.data[sourceN].A)); } else if (blend == BlendMode.Invert) { ColorRgba targetColor = target.data[targetN]; float clrTempR = (float)targetColor.R / 255.0f; float clrTempG = (float)targetColor.G / 255.0f; float clrTempB = (float)targetColor.B / 255.0f; float clrTempA = (float)targetColor.A / 255.0f; float clrTempR2 = (float)this.data[sourceN].R / 255.0f; float clrTempG2 = (float)this.data[sourceN].G / 255.0f; float clrTempB2 = (float)this.data[sourceN].B / 255.0f; float clrTempA2 = (float)this.data[sourceN].A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(this.data[sourceN].R * (1.0f - clrTempR) + targetColor.R * (1.0f - clrTempR2)))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(this.data[sourceN].G * (1.0f - clrTempG) + targetColor.G * (1.0f - clrTempG2)))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(this.data[sourceN].B * (1.0f - clrTempB) + targetColor.B * (1.0f - clrTempB2)))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)(targetColor.A + this.data[sourceN].A))); } } } }); }
/// <summary> /// Extracts a rectangular region of this Layer. If the extracted region is bigger than the original Layer, /// all new space is filled with a background color. /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="w"></param> /// <param name="h"></param> /// <param name="backColor"></param> public PixelData CloneSubImage(int x, int y, int w, int h, ColorRgba backColor) { PixelData tempLayer = new PixelData(w, h, backColor); this.DrawOnto(tempLayer, BlendMode.Solid, -x, -y); return tempLayer; }
/// <summary> /// Creates a new Pixmap from the specified <see cref="Duality.Drawing.PixelData"/>. /// </summary> /// <param name="image"></param> public Pixmap(PixelData image) { this.MainLayer = image; }
/// <summary> /// Renders a text to the specified target <see cref="Duality.Drawing.PixelData"/>. /// </summary> /// <param name="text"></param> /// <param name="target"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="clr"></param> public void RenderToBitmap(string text, PixelData target, float x, float y, ColorRgba clr) { if (this.pixelData == null) return; PixelData bitmap = this.pixelData.MainLayer; float curOffset = 0.0f; GlyphData glyphData; Rect uvRect; float glyphXOff; float glyphXAdv; for (int i = 0; i < text.Length; i++) { this.ProcessTextAdv(text, i, out glyphData, out uvRect, out glyphXAdv, out glyphXOff); Vector2 dataCoord = uvRect.Pos * new Vector2(this.pixelData.Width, this.pixelData.Height) / this.texture.UVRatio; bitmap.DrawOnto(target, BlendMode.Alpha, MathF.RoundToInt(x + curOffset + glyphXOff), MathF.RoundToInt(y), glyphData.Width, glyphData.Height, MathF.RoundToInt(dataCoord.X), MathF.RoundToInt(dataCoord.Y), clr); curOffset += glyphXAdv; } }
/// <summary> /// Renders a text to the specified target <see cref="Duality.Resources.Pixmap"/> <see cref="Duality.Drawing.PixelData"/>. /// </summary> /// <param name="text"></param> /// <param name="target"></param> /// <param name="x"></param> /// <param name="y"></param> public void RenderToBitmap(string text, PixelData target, float x = 0.0f, float y = 0.0f) { this.RenderToBitmap(text, target, x, y, ColorRgba.White); }
/// <summary> /// Retrieves the rasterized <see cref="Duality.Drawing.PixelData"/> for a single glyph. /// </summary> /// <param name="glyph">The glyph of which to retrieve the Bitmap.</param> /// <returns>The Bitmap that has been retrieved, or null if the glyph is not supported.</returns> public PixelData GetGlyphBitmap(char glyph) { Rect targetRect; int charIndex = (int)glyph > this.charLookup.Length ? 0 : this.charLookup[(int)glyph]; this.pixelData.LookupAtlas(charIndex, out targetRect); PixelData subImg = new PixelData( MathF.RoundToInt(targetRect.W), MathF.RoundToInt(targetRect.H)); this.pixelData.MainLayer.DrawOnto(subImg, BlendMode.Solid, -MathF.RoundToInt(targetRect.X), -MathF.RoundToInt(targetRect.Y)); return subImg; }
/// <summary> /// Applies a new set of rendered glyphs to the <see cref="Font"/>, adjusts its typeface metadata and clears out the <see cref="GlyphsDirty"/> flag. /// This method is used by the editor to update a Font after adjusting its properties. /// </summary> /// <param name="bitmap"></param> /// <param name="atlas"></param> /// <param name="glyphs"></param> /// <param name="height"></param> /// <param name="ascent"></param> /// <param name="bodyAscent"></param> /// <param name="descent"></param> /// <param name="baseLine"></param> public void SetGlyphData(PixelData bitmap, Rect[] atlas, GlyphData[] glyphs, int height, int ascent, int bodyAscent, int descent, int baseLine) { this.ReleaseResources(); this.glyphs = glyphs; this.GenerateCharLookup(); this.pixelData = new Pixmap(bitmap); this.pixelData.Atlas = atlas.ToList(); this.height = height; this.ascent = ascent; this.bodyAscent = bodyAscent; this.descent = descent; this.baseLine = baseLine; for (int i = 0; i < this.glyphs.Length; i++) { this.maxGlyphWidth = Math.Max(this.maxGlyphWidth, this.glyphs[i].Width); } this.UpdateKerningData(); this.GenerateTexMat(); this.glyphsDirty = false; }
/// <summary> /// Applies a new set of rendered glyphs to the <see cref="Font"/>, adjusts its typeface metadata and clears out the <see cref="GlyphsDirty"/> flag. /// This method is used by the editor to update a Font after adjusting its properties. /// </summary> /// <param name="bitmap"></param> /// <param name="atlas"></param> /// <param name="glyphs"></param> /// <param name="metrics"></param> public void SetGlyphData(PixelData bitmap, Rect[] atlas, GlyphData[] glyphs, FontMetrics metrics) { this.ReleaseResources(); this.glyphs = glyphs; this.GenerateCharLookup(); this.pixelData = new Pixmap(bitmap); this.pixelData.Atlas = atlas.ToList(); this.metrics = metrics; // Copy metrics data into local fields. // Remove this on the next major version step. this.size = metrics.Size; this.height = metrics.Height; this.ascent = metrics.Ascent; this.bodyAscent = metrics.BodyAscent; this.descent = metrics.Descent; this.baseLine = metrics.BaseLine; this.monospace = metrics.Monospace; this.maxGlyphWidth = 0; for (int i = 0; i < this.glyphs.Length; i++) { this.maxGlyphWidth = Math.Max(this.maxGlyphWidth, this.glyphs[i].Width); } this.UpdateKerningData(); this.GenerateTexture(); this.GenerateMaterial(); }
/// <summary> /// Performs a drawing operation from this Layer to a target layer. /// </summary> /// <param name="target"></param> /// <param name="blend"></param> /// <param name="destX"></param> /// <param name="destY"></param> /// <param name="width"></param> /// <param name="height"></param> /// <param name="srcX"></param> /// <param name="srcY"></param> /// <param name="colorTint"></param> public void DrawOnto(PixelData target, BlendMode blend, int destX, int destY, int width, int height, int srcX, int srcY, ColorRgba colorTint) { if (colorTint == ColorRgba.White) { this.DrawOnto(target, blend, destX, destY, width, height, srcX, srcY); return; } if (width == -1) width = this.width; if (height == -1) height = this.height; int beginX = MathF.Max(0, -destX, -srcX); int beginY = MathF.Max(0, -destY, -srcY); int endX = MathF.Min(width, this.width, target.width - destX, this.width - srcX); int endY = MathF.Min(height, this.height, target.height - destY, this.height - srcY); if (endX - beginX < 1) return; if (endY - beginY < 1) return; ColorRgba clrSource; ColorRgba clrTarget; Parallel.ForEach(Partitioner.Create(beginX, endX), range => { for (int i = range.Item1; i < range.Item2; i++) { for (int j = beginY; j < endY; j++) { int sourceN = srcX + i + this.width * (srcY + j); int targetN = destX + i + target.width * (destY + j); clrSource = this.data[sourceN] * colorTint; if (blend == BlendMode.Solid) { target.data[targetN] = clrSource; } else if (blend == BlendMode.Mask) { if (clrSource.A >= 0) target.data[targetN] = this.data[sourceN]; } else if (blend == BlendMode.Add) { clrTarget = target.data[targetN]; float alphaTemp = (float)clrSource.A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.R + clrSource.R * alphaTemp))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.G + clrSource.G * alphaTemp))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.B + clrSource.B * alphaTemp))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)clrTarget.A + (int)clrSource.A)); } else if (blend == BlendMode.Alpha) { clrTarget = target.data[targetN]; float alphaTemp = (float)clrSource.A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.R * (1.0f - alphaTemp) + clrSource.R * alphaTemp))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.G * (1.0f - alphaTemp) + clrSource.G * alphaTemp))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.B * (1.0f - alphaTemp) + clrSource.B * alphaTemp))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.A * (1.0f - alphaTemp) + clrSource.A))); } else if (blend == BlendMode.AlphaPre) { clrTarget = target.data[targetN]; float alphaTemp = (float)clrSource.A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.R * (1.0f - alphaTemp) + clrSource.R))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.G * (1.0f - alphaTemp) + clrSource.G))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.B * (1.0f - alphaTemp) + clrSource.B))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.A * (1.0f - alphaTemp) + clrSource.A))); } else if (blend == BlendMode.Multiply) { clrTarget = target.data[targetN]; float clrTempR = (float)clrTarget.R / 255.0f; float clrTempG = (float)clrTarget.G / 255.0f; float clrTempB = (float)clrTarget.B / 255.0f; float clrTempA = (float)clrTarget.A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.R * clrTempR))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.G * clrTempG))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.B * clrTempB))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)clrTarget.A + (int)clrSource.A)); } else if (blend == BlendMode.Light) { clrTarget = target.data[targetN]; float clrTempR = (float)clrTarget.R / 255.0f; float clrTempG = (float)clrTarget.G / 255.0f; float clrTempB = (float)clrTarget.B / 255.0f; float clrTempA = (float)clrTarget.A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.R * clrTempR + clrTarget.R))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.G * clrTempG + clrTarget.G))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.B * clrTempB + clrTarget.B))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)clrTarget.A + (int)clrSource.A)); } else if (blend == BlendMode.Invert) { clrTarget = target.data[targetN]; float clrTempR = (float)clrTarget.R / 255.0f; float clrTempG = (float)clrTarget.G / 255.0f; float clrTempB = (float)clrTarget.B / 255.0f; float clrTempA = (float)clrTarget.A / 255.0f; float clrTempR2 = (float)clrSource.R / 255.0f; float clrTempG2 = (float)clrSource.G / 255.0f; float clrTempB2 = (float)clrSource.B / 255.0f; float clrTempA2 = (float)clrSource.A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.R * (1.0f - clrTempR) + clrTarget.R * (1.0f - clrTempR2)))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.G * (1.0f - clrTempG) + clrTarget.G * (1.0f - clrTempG2)))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.B * (1.0f - clrTempB) + clrTarget.B * (1.0f - clrTempB2)))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)(clrTarget.A + clrSource.A))); } } } }); }
/// <summary> /// Performs a drawing operation from this Layer to a target layer. /// </summary> /// <param name="target"></param> /// <param name="blend"></param> /// <param name="destX"></param> /// <param name="destY"></param> /// <param name="width"></param> /// <param name="height"></param> /// <param name="srcX"></param> /// <param name="srcY"></param> /// <param name="colorTint"></param> public void DrawOnto(PixelData target, BlendMode blend, int destX, int destY, int width, int height, int srcX, int srcY, ColorRgba colorTint) { if (colorTint == ColorRgba.White) { this.DrawOnto(target, blend, destX, destY, width, height, srcX, srcY); return; } if (width == -1) { width = this.width; } if (height == -1) { height = this.height; } int beginX = MathF.Max(0, -destX, -srcX); int beginY = MathF.Max(0, -destY, -srcY); int endX = MathF.Min(width, this.width, target.width - destX, this.width - srcX); int endY = MathF.Min(height, this.height, target.height - destY, this.height - srcY); if (endX - beginX < 1) { return; } if (endY - beginY < 1) { return; } ColorRgba clrSource; ColorRgba clrTarget; Parallel.ForEach(Partitioner.Create(beginX, endX), range => { for (int i = range.Item1; i < range.Item2; i++) { for (int j = beginY; j < endY; j++) { int sourceN = srcX + i + this.width * (srcY + j); int targetN = destX + i + target.width * (destY + j); clrSource = this.data[sourceN] * colorTint; if (blend == BlendMode.Solid) { target.data[targetN] = clrSource; } else if (blend == BlendMode.Mask) { if (clrSource.A > 0) { target.data[targetN] = this.data[sourceN]; } } else if (blend == BlendMode.Add) { clrTarget = target.data[targetN]; float alphaTemp = (float)clrSource.A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.R + clrSource.R * alphaTemp))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.G + clrSource.G * alphaTemp))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.B + clrSource.B * alphaTemp))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)clrTarget.A + (int)clrSource.A)); } else if (blend == BlendMode.Alpha) { clrTarget = target.data[targetN]; float alphaTemp = (float)clrSource.A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.R * (1.0f - alphaTemp) + clrSource.R * alphaTemp))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.G * (1.0f - alphaTemp) + clrSource.G * alphaTemp))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.B * (1.0f - alphaTemp) + clrSource.B * alphaTemp))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.A * (1.0f - alphaTemp) + clrSource.A))); } else if (blend == BlendMode.AlphaPre) { clrTarget = target.data[targetN]; float alphaTemp = (float)clrSource.A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.R * (1.0f - alphaTemp) + clrSource.R))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.G * (1.0f - alphaTemp) + clrSource.G))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.B * (1.0f - alphaTemp) + clrSource.B))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrTarget.A * (1.0f - alphaTemp) + clrSource.A))); } else if (blend == BlendMode.Multiply) { clrTarget = target.data[targetN]; float clrTempR = (float)clrTarget.R / 255.0f; float clrTempG = (float)clrTarget.G / 255.0f; float clrTempB = (float)clrTarget.B / 255.0f; float clrTempA = (float)clrTarget.A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.R * clrTempR))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.G * clrTempG))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.B * clrTempB))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)clrTarget.A + (int)clrSource.A)); } else if (blend == BlendMode.Light) { clrTarget = target.data[targetN]; float clrTempR = (float)clrTarget.R / 255.0f; float clrTempG = (float)clrTarget.G / 255.0f; float clrTempB = (float)clrTarget.B / 255.0f; float clrTempA = (float)clrTarget.A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.R * clrTempR + clrTarget.R))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.G * clrTempG + clrTarget.G))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.B * clrTempB + clrTarget.B))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)clrTarget.A + (int)clrSource.A)); } else if (blend == BlendMode.Invert) { clrTarget = target.data[targetN]; float clrTempR = (float)clrTarget.R / 255.0f; float clrTempG = (float)clrTarget.G / 255.0f; float clrTempB = (float)clrTarget.B / 255.0f; float clrTempA = (float)clrTarget.A / 255.0f; float clrTempR2 = (float)clrSource.R / 255.0f; float clrTempG2 = (float)clrSource.G / 255.0f; float clrTempB2 = (float)clrSource.B / 255.0f; float clrTempA2 = (float)clrSource.A / 255.0f; target.data[targetN].R = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.R * (1.0f - clrTempR) + clrTarget.R * (1.0f - clrTempR2)))); target.data[targetN].G = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.G * (1.0f - clrTempG) + clrTarget.G * (1.0f - clrTempG2)))); target.data[targetN].B = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(clrSource.B * (1.0f - clrTempB) + clrTarget.B * (1.0f - clrTempB2)))); target.data[targetN].A = (byte)Math.Min(255, Math.Max(0, (int)(clrTarget.A + clrSource.A))); } } } }); }
/// <summary> /// Extracts a rectangular region of this Layer. If the extracted region is bigger than the original Layer, /// all new space is filled with a background color. /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="w"></param> /// <param name="h"></param> /// <param name="backColor"></param> public void SubImage(int x, int y, int w, int h, ColorRgba backColor) { PixelData tempLayer = new PixelData(w, h, backColor); this.DrawOnto(tempLayer, BlendMode.Solid, -x, -y); this.width = tempLayer.width; this.height = tempLayer.height; this.data = tempLayer.data; }