-
Notifications
You must be signed in to change notification settings - Fork 0
Bitmap Manipulation
The essence of a bitmap is really only the pixels it is made up of. The pointer to the bitmap's pixels can be found with the PixelPointer property; this points to the first color component in the first pixel of the bitmap; the top left. Each bitmap has the pixel format ABGR, and uses 8 bytes per channel, meaning the first value is 8 Alpha bytes, then 8 Green bytes, 8 Blue bytes, and finally 8 Red bytes.
As briefly mentioned in Bitmap Structure, bitmaps function by manipulating the SDL_Surface object, and converting that to a SDL_Texture so it can be rendered immediately during the rendering step. For the library to know when to do this surface-to-texture conversion, though, you must mark the beginning and end of bitmap manipulation by first Unlock()ing before you start changing pixel data, and then Lock()ing the surface when you're done, so that the surface can be converted and prepared for rendering. If you attempt to set or change a pixel or a region of pixels on the bitmap while the bitmap is still locked, an exception will be thrown as the bitmap was not properly unlocked before modification. Similarly, if a bitmap is about to be rendered but it is still unlocked (i.e. being edited, according to this specification), the renderer will throw an exception as the surface has not been converted to a texture yet, and thus no texture exists (it could render the old texture, but this was probably an oversight by the developer who forgot to Lock() the bitmap, and so it crashes instead). This exception can be cumbersome to pin down, as the exception is not thrown in the context of the bitmap that was never locked. You must therefore take great care in unlocking and locking the bitmap when manipulating it. This also applies to chunky bitmaps in their entirety.
Since chunky bitmaps work differently than regular bitmaps, and because not all optimizations are available to chunky bitmaps, some methods will be marked with (REG). These are methods that are only available for use on normal bitmaps, and will either throw an exception or have undefined behaviour when used on a chunky bitmap. Furthermore, methods that do not manipulate the bitmap itself but instead return a new bitmap are marked with (NEW). These methods can be used on locked bitmaps too, as they do not modify themself.
To not bore you with the details of the underlying data structure too much though, there are a plethora of methods available that you can use to manipulate bitmaps. The simpler methods are listed below; note that most methods have all permutations of argument substitions available to them, such as using a Point object rather than an x and y integer argument.
-
Clear(): Clears the entire content of the bitmap. -
GetPixel(int x, int y): Returns theColorobject that the pixel at the specified coordinate represents. - (REG)
GetPixelFast(int x, int y): Identical toGetPixel, but does not validate that the coordinates are within bounds. This is useful for methods that read or write many pixels in a specific region, because the bounds can just be checked once at the beginning of the calling method rather than with every pixel read/write operation. This can save quite a bit of time. -
SetPixel(int x, int y, Color clr): Sets the pixel at the specified coordinate to the specified color. -
DrawLine(Point p1, Point p2, Color clr): Draws a line between the two points. -
DrawLines(Color clr, Point[] points): Draws a line between all the specified points. It does not connect the first line with the last line by itself. -
DrawCircle(int ox, int oy, int radius, Color clr): Draws a circle at the given origin with the given radius. -
FillCircle(int ox, int oy, int radius, Color clr): Draws and fills a circle at the given origin with the given radius. -
DrawQuadrant(int ox, int oy, int radius, Location loc, Color clr): Draws only a quadrant of a circle at the given origin with the given radius, based on the location of the quadrant that needs to be drawn. -
FillQuadrant(int ox, int oy, int radius, Location loc, Color clr): Draws and fills only a quadrant of a circle at the given origin with the given radius, based on the location of the quadrant that needs to be drawn. -
DrawRect(Rect rect, Color clr): Draws the outline of a rectangle. -
FillRect(Rect rect, Color clr): Draws and fills a rectangle. -
DrawGradientLine(Point p1, Point p2, Color c1, Color c2): Draws a line between two points, but the color of each point is determined by the distance to the two origin points. -
FillGradientRect(Rect rect, Color c1, Color c2, Color c3, Color c4): Draws and fills a rectangle, bilinearly interpolation from the four given colors, which are used at the corners. The order of these colors is top left, top right, bottom left, and bottom right. -
FillGradientRect(Rect rect, Color c1, Color c2, bool Flipped = false, bool UseTriangles = true): Similar to the method above, but instead interpolates over two colors (top left, bottom right), or when flipped (top right, bottom left). If it needs to use triangles, it will instead draw and fill two triangles and bilinearly interpolates over both of them, rather than doing it in one go. The result is slightly different, but the performance also changes. -
FillTriangle(Point p0, Point p1, Point p2, Color clr): Draws and fills a triangle. -
FillGradientTriangle(Vertex v0, Vertex v1, Vertex v2): Draws and fills a triangle, and the color of each pixel is determined by the barycentric coordinates of the physical coordinates of the pixel that is being drawn. The vertices specify the positions of the crossing of two edges and the color of that point. -
FillGradientCircle(int ox, int oy, int Radius, Color c1, Color c2): Draws and fills a circle, and the color of each pixel is determined by the distance from the pixel to the origin (radial gradient). -
FillGradientQuadrant(int ox, int oy, int radius, Location loc, Color c1, Color c2): Draws and fills only a quadrant of a circle at the given origin with the given radius, based on the location of the quadrant that needs to be drawn. The color of each pixel is determined by the distance from the pixel to the origin (radial gradient). -
FillGradientRectOutside(Rect outside, Rect inside, Color c1, Color c2, bool FillInside = true): Particularly useful for drawing shadows around a rectangle, this method fills the region inside of the outside rectangle, but outside of the inside rectangle. This method is provided because drawing proper corners for non-symmetrical rectangles can be tricky. -
FlipVertically(Rect rect): Flips the content of the rectangle vertically. -
FlipHorizontally(Rect rect): Flips the content of the rectangle horizontally. -
ShiftVertically(int startY, int rowCount, int yShift, bool clearOrigin): Shifts a region starting atstartYwith a height ofrowCountup or down based onyShift, and optionally clears the original region before the shift. No horizontal version of this method exists as this will be painfully slow. - (NEW)
Blur(int weight, float scale, bool transparentEdges): Applies a box-blur algorithm on the bitmap with a particular window size (weight), and then scales the colors up or down. - (NEW)
BlurExcludeRectangle(Rect inside, int weight, bool transparentEdges): The same asBlur, but it excludes the inside rectangle. - (NEW)
WithThreshold(float threshold): Filters out all pixels that do not have at least one of the RGB components above the threshold, essentially only selecting the bright colors. - (NEW)
Bloom(float scale, float threshold, int weight): Simulates a Bloom Filter by taking the brightest colors of the image withWithThreshold, thenBlurs the result, and additively blends the result with the source bitmap. - (NEW)
Resize(Size newSize, Size? chunkSize = null): Resizes the image (without pixel modification! So no scaling, not even nearest-neighbour) to a new size, discarding any pixels that fall outside of the new bitmap. - (NEW) (REG)
ApplyHue(int Hue, Rect rect): Changes the hue of the original image in a rectangle. -
SaveToPNG(string filename, bool transparency = true, bool indexed = false, int maxPaletteSize = 0): Saves the bitmap to a PNG file. If the file should be indexed and the palette size of the bitmap is greater thanmaxPaletteSizeor 255 if not set, it will iteratively reduce the palette of the bitmap until it fits. Note that this can be very slow, because it will at worst compress a palette of 16.777.216 distinct colors to a palette of 255! This method can be used on locked bitmaps too.
One of the methods available to Bitmaps is the Build(Rect destRect, Bitmap srcBitmap, Rect srcRect, BlendMode blendMode = BlendMode.Blend) method, which takes a source bitmap and rectangle, and a destination rectangle. The basic premise of this method is that it blits, or builds, the source bitmap onto the destination bitmap (the bitmap that calls the method). The source rectangle determines the region of the source bitmap that will be blitted, and the destination rectangle determines the area that that same rectangle will take up in the destination bitmap. Note that the size of these rectangles may not be the same; in this case, the source rectangle region of the source bitmap is scaled, and Nearest-Neighbour interpolation is applied.
Furthermore, you can choose how to blend the two bitmaps. Note that this is not the same as the BlendMode property (which is hardware texture blending during rendering, i.e. how it blends with completely unrelated bitmaps that overlap this bitmap on the screen), but this is surface blending, which is applied only to this particular blit operation and dictates how they are blended onto the destination surface using software. The most common blend mode is BlendMode.Blend, which is alpha blending. Alternatively you can choose to set the blend mode to BlendMode.None, which will directly override the pixels in the destination bitmap with those from the source bitmap. This has only limited uses, but it is twice as fast as with any other blend mode, and is therefore recommended whenever possible.
Another slightly niche method is the Mask(Bitmap mask, Bitmap source, Rect srcRect) method. It essentially copies all the pixels within the source rectangle from the source bitmap into a new bitmap, unless the pixel in the same location in the mask bitmap is fully transparent, i.e. has an alpha of 0. This can be used to clip an arbitrary image, say a rectangular profile picture to an arbitrary shape (the mask). If the mask bitmap were a fully-filled circle of any color, and you used this as a mask on the rectangular profile picture bitmap, you would get the profile picture bitmap back, but in a round shape instead of a circular one. The color and transparency of the mask is irrelevant; the only thing that matters is whether a pixel has an alpha value of 0 (reject source pixel), or non-zero (accept source pixel). The mask could be every color in the rainbow, as long as you take care of the pixel's transparency (i.e. zero or non-zero). The mask must also be the same size as the source bitmap. Also noteworthy is that this is a static method on the Bitmap class, and performs only on the mask and source bitmaps; there is no this in this context.
To draw text or characters (glyphs) in the library, they must be drawn on a bitmap. To get started with this, the Bitmap must have its Font property set to a valid font. Depending on what you want to draw, you can then use DrawText(string text, int x, int y, int width, int height, Color clr, DrawOptions drawOptions = DrawOptions.LeftAlign) or DrawGlyph to draw the text or glyph you want. The width and height parameters are optional; if these are provided, the text will be resized to fit the rectangle regardless of font size (this will only be used for resolution, not for determining how big to draw the text or glyph). The draw options include left, center or right alignment, and also contain options like drawing the text or glyph in bold, italics, underlined, and/or strikethrough. You can also include the Aliased option, which draws the text or glyph without transparency and only draws whole pixels. This is much faster than the default, Anti-Aliased option, but looks much worse.
The difference between DrawText and DrawGlyph is that DrawGlyph is capable of rendering specific rules and formats for individual characters (like FontAwesome), whereas DrawText may not support such fonts.
To determine what the size of the text that is to be drawn will be, you can use the TextSize(string text, DrawOptions drawOptions = 0) method (and an overload for glyphs exists too). This method takes a string and draw options, and returns the width and the height of the text as it will look when it is drawn with DrawText or DrawGlyph. This can be used before drawing anything, or even without drawing anything at all, as long as the bitmap's Font property is set to a valid font. This method is a shortcut for font.TextSize, which contains the logic itself.
For more information about how to create fonts, see Fonts.
All bitmaps must be disposed by calling the Dispose() method before it goes out of scope and is GC'd. That way the underlying SDL surface and texture can bee destroyed/freed, otherwise you will introduce a memory leak.