├── blending.md ├── context.md ├── fonts ├── Roboto-Regular_1.ttf └── Ubuntu-Regular_1.ttf ├── images.md ├── images ├── background.png ├── blendColorBlend.png ├── blendColorBurnBlend.png ├── blendColorDodgeBlend.png ├── blendColors.png ├── blendDarkenBlend.png ├── blendDifferenceBlend.png ├── blendExcludeMaskBlend.png ├── blendExclusionBlend.png ├── blendHardLightBlend.png ├── blendHueBlend.png ├── blendLightenBlend.png ├── blendLuminosityBlend.png ├── blendMaskBlend.png ├── blendMultiplyBlend.png ├── blendNormalBlend.png ├── blendOverlayBlend.png ├── blendOverwriteBlend.png ├── blendSaturationBlend.png ├── blendScreenBlend.png ├── blendSoftLightBlend.png ├── blendSubtractMaskBlend.png ├── bool_exclude.png ├── bool_intersect.png ├── bool_subtract.png ├── bool_union.png ├── caps.png ├── checkers.png ├── circleProfile.png ├── colors.png ├── context.png ├── grays.png ├── heart1.png ├── heart2.png ├── heart3.png ├── hues.png ├── intro1.png ├── intro2.png ├── intro3.png ├── intro4.png ├── joins.png ├── letterProfile.png ├── letterProfile1.png ├── letterProfile2.png ├── loopOrderXY.png ├── loopOrderYX.png ├── masking1.png ├── masking2.png ├── masks.png ├── notContext.png ├── overwriteBlend.png ├── path1.png ├── pixels.png ├── premultipliedAlpha1.png ├── premultipliedAlpha2.png ├── profile.jpeg ├── scan1.png ├── scan2.png ├── scan3.png ├── scan4.png ├── scan5.png ├── smallProfile.png ├── svgpath1.png ├── svgpath2.png ├── svgpath3.png ├── svgpath4.png ├── svgpath5.png ├── svgpath6.png ├── svgpath7.png ├── svgpath8.png ├── svgpath9.png ├── testTexture.png ├── text1.png ├── text2.png ├── textIsHard.png ├── textKerning1.png ├── textKerning2.png ├── textKerning3.png ├── textLayout.png ├── textMetrics.png ├── textSpans.png ├── winding1.png ├── winding2.png ├── winding3.png ├── winding4.png ├── winding5.png └── winding6.png ├── paths.md ├── readme.md ├── src ├── blending.nim ├── context.nim ├── extract.nim ├── genblends.nim └── tutorial.nim ├── text.md └── tutorial.md /blending.md: -------------------------------------------------------------------------------- 1 | # Blending and Masking 2 | 3 | Blending and masking are two techniques that are commonly used in digital graphics to create special effects or to manipulate the appearance of images. 4 | 5 | Blending refers to the process of combining two or more images or layers in order to create a single, composite image. This can be done in a variety of ways, such as by overlaying one image on top of another, or by blending the colors of the two images together. Pixie supports a variety of blending modes, which are different algorithms that can be used to combine two images in different ways. Some common blending modes include "normal," "multiply," "screen," and "overlay." 6 | 7 | Masking is a technique that is similar to blending, but it involves using an image or layer to selectively hide or reveal parts of another image or layer. This can be done by using the pixels in the masking image as an alpha channel, which defines the transparency of the image being masked. Pixie supports a variety of blending modes that can be used for masking, such as "mask" and "subtract" or "exclude." 8 | 9 | Overall, blending and masking are powerful techniques that can be used to create a wide range of effects in digital graphics. They can be used to create composite images, add special effects to images, or to selectively hide or reveal parts of an image. 10 | 11 | Starting with background image by Hulki Okan Tabak @hulkiokantabak of Bosphorus Coast, Istanbul, Turkey: 12 | 13 | ![blend image](images/background.png) 14 | 15 | Here is what blends are capable of: 16 | 17 | ### NormalBlend 18 | 19 | The default blend for everything. Just blends the colors normally. 20 | 21 | ![blend image](images/blendNormalBlend.png) 22 | 23 | ### DarkenBlend 24 | 25 | ![blend image](images/blendDarkenBlend.png) 26 | 27 | ### MultiplyBlend 28 | 29 | ![blend image](images/blendMultiplyBlend.png) 30 | 31 | ### ColorBurnBlend 32 | 33 | ![blend image](images/blendColorBurnBlend.png) 34 | 35 | ### LightenBlend 36 | 37 | ![blend image](images/blendLightenBlend.png) 38 | 39 | ### ScreenBlend 40 | 41 | ![blend image](images/blendScreenBlend.png) 42 | 43 | ### ColorDodgeBlend 44 | 45 | ![blend image](images/blendColorDodgeBlend.png) 46 | 47 | ### OverlayBlend 48 | 49 | ![blend image](images/blendOverlayBlend.png) 50 | 51 | ### SoftLightBlend 52 | 53 | ![blend image](images/blendSoftLightBlend.png) 54 | 55 | ### HardLightBlend 56 | 57 | ![blend image](images/blendHardLightBlend.png) 58 | 59 | ### DifferenceBlend 60 | 61 | ![blend image](images/blendDifferenceBlend.png) 62 | 63 | ### ExclusionBlend 64 | 65 | ![blend image](images/blendExclusionBlend.png) 66 | 67 | ### HueBlend 68 | 69 | ![blend image](images/blendHueBlend.png) 70 | 71 | ### SaturationBlend 72 | 73 | ![blend image](images/blendSaturationBlend.png) 74 | 75 | ### ColorBlend 76 | 77 | ![blend image](images/blendColorBlend.png) 78 | 79 | ### LuminosityBlend 80 | 81 | ![blend image](images/blendLuminosityBlend.png) 82 | 83 | ## Masking 84 | 85 | Masking is just a special blend mode. 86 | 87 | ### Use MaskBlend to simulate a intersection of masks. 88 | 89 | MaskBlend is the default blend for masking operations. 90 | 91 | ![blend image](images/bool_intersect.png) 92 | 93 | ### Use NormalBlend to simulate a union of masks. 94 | 95 | ![blend image](images/bool_union.png) 96 | 97 | ### Use SubtractMaskBlend to simulate a subtraction of masks. 98 | 99 | ![blend image](images/bool_subtract.png) 100 | 101 | ### Use ExcludeMaskBlend to simulate a exclusion of masks. 102 | 103 | ![blend image](images/bool_exclude.png) 104 | 105 | 120 | 121 | ## OverwriteBlend 122 | 123 | `OverwriteBlend` is special in that it copies the data from one image into another as fast as possible. 124 | 125 | This blend is only safe to use on to an image you know is empty. Usually an image that was just created. 126 | Otherwise odd artifacts might or might not will happen based on optimization modes. 127 | 128 | You can replace `NormalBlend` with `OverwriteBlend` if you know that the image you are drawing to is empty to speed up the process. 129 | Its very common to draw things into just created empty images. 130 | 131 | In this example, using `OverwriteBlend` here is faster as we know that `background` image was just created and is blank: 132 | 133 | ```nim 134 | import pixie 135 | let 136 | background = newImage(100, 100) 137 | profile = readImage("images/circleProfile.png") 138 | background.draw(profile, blendMode = OverwriteBlend) 139 | background.writeFile("images/overwriteBlend.png") 140 | ``` 141 | 142 | ![blend image](images/overwriteBlend.png) 143 | -------------------------------------------------------------------------------- /context.md: -------------------------------------------------------------------------------- 1 | 2 | ## JS-Canvas and Cairo inspired API 3 | 4 | Are you familia with [JS canvas](https://developer.mozilla.org/en-US/images/Web/API/Canvas_API)? Or maybe [Cairo](https://www.cairographics.org/documentation/)? Do you have some quick code to port? This API is for you. Simply create a context object and apply different operations to it. 5 | 6 | First create context with the canvas size you want: 7 | 8 | ```nim 9 | let ctx = newContext(newImage(160, 160)) 10 | ``` 11 | 12 | Then you can fill the entire canvas with a solid color: 13 | 14 | ```nim 15 | ctx.beginPath() 16 | ctx.fillStyle = "#bdc3c7" 17 | ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32) 18 | ``` 19 | 20 | Then you can draw a shape: 21 | 22 | ```nim 23 | ctx.beginPath() 24 | ctx.fillStyle = "#2980b9" 25 | ctx.moveTo(20, 20) 26 | ctx.lineTo(140, 20) 27 | ctx.lineTo(80, 140) 28 | ctx.closePath() 29 | ctx.fill() 30 | ``` 31 | 32 | We can also stroke the shape (that is still in the buffer): 33 | 34 | ```nim 35 | ctx.strokeStyle = "#FFFFFF" 36 | ctx.lineWidth = 8 37 | ctx.stroke() 38 | ``` 39 | 40 | 41 | After that you can simply save it to a file: 42 | 43 | ```nim 44 | ctx.image.writeFile("images/context.png") 45 | ``` 46 | 47 | ![example output](images/context.png) 48 | 49 | > Note: Context api is there for compatibility and there is a more advanced api that can do even more operations. 50 | 51 | The rest of the tutorial will not use the `Context API` but use the normal pixie API. 52 | 53 | Here is how you would draw the same thing in normal pixie: 54 | 55 | ```nim 56 | let image1 = newImage(160, 160) 57 | image1.fill("#bdc3c7") 58 | image1.fillPath("M 20 20 L 140 20 L 80 140 z", "#2980b9") 59 | image1.strokePath("M 20 20 L 140 20 L 80 140 z", "#FFFFFF", strokeWidth = 8) 60 | image1.writeFile("images/notContext.png") 61 | ``` 62 | 63 | And see its the same thing: 64 | 65 | ![example output](images/notContext.png) 66 | 67 | Its up to you what you to choose: an API you might already know or some thing more concise. 68 | -------------------------------------------------------------------------------- /fonts/Roboto-Regular_1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/fonts/Roboto-Regular_1.ttf -------------------------------------------------------------------------------- /fonts/Ubuntu-Regular_1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/fonts/Ubuntu-Regular_1.ttf -------------------------------------------------------------------------------- /images.md: -------------------------------------------------------------------------------- 1 | # Images 2 | 3 | Pixie is all about pixels. Images can be thought of as two-dimensional arrays of pixels, with each pixel representing a specific color. 4 | 5 | ![path](images/pixels.png) 6 | 7 | Each color is stored as RGBA bytes. First byte is red 0-255, followed by green 0-255, followed by blue 0-255, and finally followed by alpha 0-255. 8 | 9 | ![path](images/colors.png) 10 | 11 | ## Color types 12 | 13 | There are several color types in Pixie for different use cases: 14 | 15 | * `Color` type means `float32` typed `Straight Alpha` where each value is 0.0 to 1.0. 16 | * `ColorRGBA` types means `uint8` typed `Straight Alpha`. 17 | * `ColorRGBX` type means `uint8` typed `Premultiplied Alpha` were each value is 0-255 and no value can be higher then alpha value. 18 | 19 | All images and most operations in pixie are done in the `ColorRGBX` type. 20 | 21 | ## Premultiplied Alpha 22 | 23 | Pixie stores all pixels in `Premultiplied Alpha` format as apposed to `Straight Alpha`. 24 | 25 | 26 | Premultiplied Alpha can be complex to understand. 27 | I did not get it at first. 28 | But as we started to optimize things it became essential for high performance. 29 | You probably need to understand `Premultiplied Alpha` just a little bit to reap its benefits. 30 | 31 | `Straight Alpha` is probably what you are used to. 32 | Each Color pixel has a value from 0.0 - 1.0 (or 0 to 255 when dealing with bytes). 33 | 34 | But this is not the case with Premultiplied Alpha each color can never be larger then the alpha color. So if alpha is at 0.5 no color channel value can be larger then 0.5. 35 | 36 | In effect each color is 0.0 - 1.0 is stored already multiplied by alpha. 37 | Thats were the name comes from. 38 | 39 | ![path](images/premultipliedAlpha1.png) 40 | 41 | The magic of performance comes from blending colors together. 42 | When we blend two `Straight Alpha` colors together we almost need to convert them to `Premultiplied Alpha` variant add them and then write them back. 43 | Imagine doing this for every pixel it get quite slow. 44 | 45 | But the good news is that you only need to be vaguely aware of this. 46 | Pixie loads everything into `Premultiplied Alpha` when reading files and it writes everything back to `Straight Alpha` when saving them. 47 | 48 | ![path](images/premultipliedAlpha2.png) 49 | 50 | The `Premultiplied Alpha` and `Straight Alpha` is mostly just contained internally as optimization. 51 | You only really need to worry about this when you are using Pixie together with some other library like OpenGL. 52 | 53 | But even in OpenGL I recommend keeping your operation in `Premultiplied Alpha` state. 54 | 55 | ## Accessing pixels 56 | 57 | You can read and writes pixels by using `image[x, y]`. It is very common to go through all the pixels using a x/y for loop. 58 | 59 | ![path](images/loopOrderXY.png) 60 | 61 | We recommend going with Y in the outer loop and X in the inner loop because that accesses the pixels the way they are layed out in memory and is much faster then doing the X first, which causes slowdowns by access the memory out of order. 62 | 63 | ![path](images/loopOrderYX.png) 64 | 65 | This can achieve **3x** speed up in many cases. 66 | 67 | If you know that you are never accessing image out of bounds you could remove the out-of-bounds checks to speed up your code by using `image.unsafe[x, y]` instead. 68 | 69 | ```nim 70 | for y in 0 ..< image.height: 71 | for x in 0 ..< image.width: 72 | var rgbx = image.unsafe[x, y] 73 | var color = rgbx.color() 74 | image.unsafe[x, y] = color.rgbx() 75 | ``` 76 | 77 | Keep in mind that pixie uses premultiplied alpha colors internally and you might need to convert it to a `ColorRgba` - a `uint8` color or a `Color` that uses `float32` instead. 78 | 79 | 80 | ## Image Formats 81 | 82 | `PNG` (Portable Network Graphics) is a lossless image format that was designed to replace the `GIF` format and improve upon it. `PNG` supports both grayscale and full-color images, as well as alpha transparency, which allows pixels to be partially transparent. PNG was created in the mid-1990s as an open standard. It is a popular choice for web graphics because it supports lossless compression, which means that it can be compressed without losing any image quality. 83 | 84 | `JPEG` (Joint Photographic Experts Group) is a lossy image format that was designed for digital photography. `JPEG` uses a complex algorithm to compress images, resulting in smaller file sizes compared to lossless formats like PNG. However, this compression comes at the cost of some image quality, as some of the image data is lost during the compression process. `JPEG` is a popular choice for photographs because it can produce small file sizes while still maintaining a high level of visual quality. 85 | 86 | `GIF` (Graphics Interchange Format) is an image format that was developed in the late 1980s. `GIF` supports up to 8 bits per pixel, which allows it to display 256 distinct colors (without hacks). `GIF` is best known for its support for animation, as it allows multiple images to be combined into a single file and displayed in sequence. `GIF` is a lossless format, which means that it can be compressed without losing any image quality. However, due to the limited color depth and the lossless nature of the format, `GIF` files are usually much larger than other image formats like `PNG` or `JPEG`. 87 | 88 | `BMP` (Bitmap Image File) is a simple image format that was developed by Microsoft. `BMP` files store image data in a raster graphics format, meaning that they are made up of a grid of pixels, each with a specific color. `BMP` is a uncompressed format, which means that it can be used without losing any image quality. However, `BMP` files can be quite large due to the lack of advanced compression techniques used by other formats. `BMP` is not as widely used as other image formats, but it is still supported for historical reasons because is used by many win32 APIs and older windows applications. 89 | 90 | `PPM` (Portable Pixmap) is a simple image file format used for storing bitmap images. It was developed in the early 1980s as a cross-platform alternative to the `GIF` format, which at the time was patented and required a license to use. It is mostly used for compatibility on Linux and X11. 91 | 92 | `QOI` (Quite OK Image) format is a new, efficient, and fast lossless image format with a simple encoder and decoder. Its specification can be found on a single page, making it easy to understand and implement. Despite its simplicity, `QOI` is able to achieve compression levels similar to those of `PNG`. The main drawback of `QOI` is its rarity, which may make it difficult to find and use as there are virtual no tools or applications that support it. 93 | 94 | In general, `PNG` is a good choice for images that need to be high quality and support transparency, such as graphics for the web. `JPEG` is a good choice for photographs and other images with many colors, as it can produce small file sizes while still maintaining a good level of quality. `GIF` is best for simple graphics and animations, while `BMP` and `PPM` is only required for compatibility. 95 | -------------------------------------------------------------------------------- /images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/background.png -------------------------------------------------------------------------------- /images/blendColorBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendColorBlend.png -------------------------------------------------------------------------------- /images/blendColorBurnBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendColorBurnBlend.png -------------------------------------------------------------------------------- /images/blendColorDodgeBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendColorDodgeBlend.png -------------------------------------------------------------------------------- /images/blendColors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendColors.png -------------------------------------------------------------------------------- /images/blendDarkenBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendDarkenBlend.png -------------------------------------------------------------------------------- /images/blendDifferenceBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendDifferenceBlend.png -------------------------------------------------------------------------------- /images/blendExcludeMaskBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendExcludeMaskBlend.png -------------------------------------------------------------------------------- /images/blendExclusionBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendExclusionBlend.png -------------------------------------------------------------------------------- /images/blendHardLightBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendHardLightBlend.png -------------------------------------------------------------------------------- /images/blendHueBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendHueBlend.png -------------------------------------------------------------------------------- /images/blendLightenBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendLightenBlend.png -------------------------------------------------------------------------------- /images/blendLuminosityBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendLuminosityBlend.png -------------------------------------------------------------------------------- /images/blendMaskBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendMaskBlend.png -------------------------------------------------------------------------------- /images/blendMultiplyBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendMultiplyBlend.png -------------------------------------------------------------------------------- /images/blendNormalBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendNormalBlend.png -------------------------------------------------------------------------------- /images/blendOverlayBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendOverlayBlend.png -------------------------------------------------------------------------------- /images/blendOverwriteBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendOverwriteBlend.png -------------------------------------------------------------------------------- /images/blendSaturationBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendSaturationBlend.png -------------------------------------------------------------------------------- /images/blendScreenBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendScreenBlend.png -------------------------------------------------------------------------------- /images/blendSoftLightBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendSoftLightBlend.png -------------------------------------------------------------------------------- /images/blendSubtractMaskBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/blendSubtractMaskBlend.png -------------------------------------------------------------------------------- /images/bool_exclude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/bool_exclude.png -------------------------------------------------------------------------------- /images/bool_intersect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/bool_intersect.png -------------------------------------------------------------------------------- /images/bool_subtract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/bool_subtract.png -------------------------------------------------------------------------------- /images/bool_union.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/bool_union.png -------------------------------------------------------------------------------- /images/caps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/caps.png -------------------------------------------------------------------------------- /images/checkers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/checkers.png -------------------------------------------------------------------------------- /images/circleProfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/circleProfile.png -------------------------------------------------------------------------------- /images/colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/colors.png -------------------------------------------------------------------------------- /images/context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/context.png -------------------------------------------------------------------------------- /images/grays.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/grays.png -------------------------------------------------------------------------------- /images/heart1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/heart1.png -------------------------------------------------------------------------------- /images/heart2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/heart2.png -------------------------------------------------------------------------------- /images/heart3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/heart3.png -------------------------------------------------------------------------------- /images/hues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/hues.png -------------------------------------------------------------------------------- /images/intro1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/intro1.png -------------------------------------------------------------------------------- /images/intro2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/intro2.png -------------------------------------------------------------------------------- /images/intro3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/intro3.png -------------------------------------------------------------------------------- /images/intro4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/intro4.png -------------------------------------------------------------------------------- /images/joins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/joins.png -------------------------------------------------------------------------------- /images/letterProfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/letterProfile.png -------------------------------------------------------------------------------- /images/letterProfile1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/letterProfile1.png -------------------------------------------------------------------------------- /images/letterProfile2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/letterProfile2.png -------------------------------------------------------------------------------- /images/loopOrderXY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/loopOrderXY.png -------------------------------------------------------------------------------- /images/loopOrderYX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/loopOrderYX.png -------------------------------------------------------------------------------- /images/masking1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/masking1.png -------------------------------------------------------------------------------- /images/masking2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/masking2.png -------------------------------------------------------------------------------- /images/masks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/masks.png -------------------------------------------------------------------------------- /images/notContext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/notContext.png -------------------------------------------------------------------------------- /images/overwriteBlend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/overwriteBlend.png -------------------------------------------------------------------------------- /images/path1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/path1.png -------------------------------------------------------------------------------- /images/pixels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/pixels.png -------------------------------------------------------------------------------- /images/premultipliedAlpha1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/premultipliedAlpha1.png -------------------------------------------------------------------------------- /images/premultipliedAlpha2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/premultipliedAlpha2.png -------------------------------------------------------------------------------- /images/profile.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/profile.jpeg -------------------------------------------------------------------------------- /images/scan1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/scan1.png -------------------------------------------------------------------------------- /images/scan2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/scan2.png -------------------------------------------------------------------------------- /images/scan3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/scan3.png -------------------------------------------------------------------------------- /images/scan4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/scan4.png -------------------------------------------------------------------------------- /images/scan5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/scan5.png -------------------------------------------------------------------------------- /images/smallProfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/smallProfile.png -------------------------------------------------------------------------------- /images/svgpath1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/svgpath1.png -------------------------------------------------------------------------------- /images/svgpath2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/svgpath2.png -------------------------------------------------------------------------------- /images/svgpath3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/svgpath3.png -------------------------------------------------------------------------------- /images/svgpath4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/svgpath4.png -------------------------------------------------------------------------------- /images/svgpath5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/svgpath5.png -------------------------------------------------------------------------------- /images/svgpath6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/svgpath6.png -------------------------------------------------------------------------------- /images/svgpath7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/svgpath7.png -------------------------------------------------------------------------------- /images/svgpath8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/svgpath8.png -------------------------------------------------------------------------------- /images/svgpath9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/svgpath9.png -------------------------------------------------------------------------------- /images/testTexture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/testTexture.png -------------------------------------------------------------------------------- /images/text1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/text1.png -------------------------------------------------------------------------------- /images/text2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/text2.png -------------------------------------------------------------------------------- /images/textIsHard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/textIsHard.png -------------------------------------------------------------------------------- /images/textKerning1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/textKerning1.png -------------------------------------------------------------------------------- /images/textKerning2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/textKerning2.png -------------------------------------------------------------------------------- /images/textKerning3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/textKerning3.png -------------------------------------------------------------------------------- /images/textLayout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/textLayout.png -------------------------------------------------------------------------------- /images/textMetrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/textMetrics.png -------------------------------------------------------------------------------- /images/textSpans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/textSpans.png -------------------------------------------------------------------------------- /images/winding1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/winding1.png -------------------------------------------------------------------------------- /images/winding2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/winding2.png -------------------------------------------------------------------------------- /images/winding3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/winding3.png -------------------------------------------------------------------------------- /images/winding4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/winding4.png -------------------------------------------------------------------------------- /images/winding5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/winding5.png -------------------------------------------------------------------------------- /images/winding6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/pixiebook/c62ce64d5b14f80f21bce8337e900b7323b789ea/images/winding6.png -------------------------------------------------------------------------------- /paths.md: -------------------------------------------------------------------------------- 1 | 2 | # Paths 3 | 4 | In computer graphics, a path is a series of connected strokes that can be used to create a shape or outline. Paths can be open, meaning that they do not form a closed shape, or closed, meaning that they form a closed shape. Vector graphics are graphics that are created using mathematical equations to draw lines and curves. These graphics are made up of paths, and they can be scaled to any size without losing quality because they are not made up of pixels like bitmap graphics. 5 | 6 | Vector graphics are often used for logos, icons, text and other graphics that need to be resized or modified frequently because they can be easily edited and scaled without losing quality. They are also useful for creating graphics that need to be printed at a high resolution because they can be rendered smoothly at any size. 7 | 8 | A common format to describe vector path is the SVG Path command string. 9 | 10 | ![path](images/svgpath1.png) 11 | 12 | Lets take this format apart. 13 | 14 | ![path](images/svgpath2.png) 15 | 16 | First "M" part means move is a move the path pen without drawing anything. 17 | 18 | ![path](images/svgpath3.png) 19 | 20 | The second is the "L" line command tell the path pen to draw a line. 21 | 22 | Lets look at more complex vector shape: 23 | 24 | ![path](images/svgpath4.png) 25 | 26 | A shape like this start can be described by but the SVG path above. It contains "M" move, "C" curves and "L" lines and "H" horizontal lines. 27 | 28 | ![path](images/svgpath5.png) 29 | 30 | First command of this start is a move command. 31 | 32 | ![path](images/svgpath6.png) 33 | 34 | Then comes the "C" curve command. It describes a bezier curve. 35 | 36 | ![path](images/svgpath7.png) 37 | 38 | Then comes the "L" line command. This then repeat for all of the commands. 39 | 40 | Pixie can `parsePath("")` SVG path specification or you can build by calling more advanced methods on the Path objects itself. 41 | 42 | SVG Command | Path Command | Description 43 | ------------- | ------------------ | ----------------------------------- 44 | M m | .moveTo | move to 45 | L l | .lineTo | line to 46 | H h | .lineTo | horizontal line to 47 | V v | .lineTo | vertical line to 48 | C c S s | .bezierCurveTo | cubic curve to 49 | Q q T t | .quadraticCurveTo | quadratic curve to 50 | A a | .ellipticalArcTo | arc to 51 | z | .closePath | close path 52 |   | .polygon | N sided polygon 53 |   | .circle | circle 54 |   | .ellipse | ellipse 55 |   | .rect | rectangle 56 |   | .roundedRect | rounded rectangle 57 |   | .arc | circular arc 58 |   | .arcTo | circular arc using control points 59 |   | .addPath | add another path to this path 60 | 61 | ## Rasterization 62 | 63 | At the core of vector graphics there is rasterization. 64 | 65 | > [Rasterisation](https://en.wikipedia.org/wiki/Rasterisation) (or rasterization) is the task of taking an image described in a **vector graphics format** (shapes) and converting it into a **raster image** (a series of pixels). 66 | 67 | 68 | ## Discretizeing 69 | 70 | ![path](images/svgpath8.png) 71 | 72 | When we fill paths we actually just want lines. So we subdivide the curves into lines. 73 | 74 | ![path](images/svgpath9.png) 75 | 76 | We keep subdividing the line segments path until the error of a segment is small enough. 77 | 78 | This varies based on what is the scale of the path. A large path can subdivide into many many line segments while a little curve smaller then a pixel can be a just a single segment. 79 | 80 | ## Path Filling 81 | 82 | ![path](images/path1.png) 83 | 84 | Finally we have an outline of an images all made out of line segments. We can start filling the path. 85 | 86 | ![path](images/scan1.png) 87 | 88 | To rasterize we perform a ray scan for each row of pixels. Here we chose to scan from left to right, top to bottom, but really you can scan in any direction as long as your consistent 89 | 90 | ![path](images/scan2.png) 91 | 92 | When our scan ray hits a line, we start filling that row. 93 | 94 | ![path](images/scan3.png) 95 | 96 | Once we hit another line we stop. 97 | 98 | ![path](images/scan4.png) 99 | 100 | We do that for every row until we have a complete image. 101 | 102 | ![path](images/scan5.png) 103 | 104 | To have the images be smooth we can Anti-Alias. To produce smooth shapes we can cast a ray multiple times per pixel. 105 | 106 | ## Winding Rules 107 | 108 | SVG uses a set of winding rules to determine how the fill of a shape is calculated. These rules specify how the direction of the paths of a shape are interpreted in relation to the fill of the shape. 109 | 110 | There are two main winding rules: the `non-zero` winding rule and the `even-odd` winding rule. 111 | 112 | The `non-zero` winding rule states that a point is inside the shape if the number of clockwise path segments around the point is not equal to the number of counter-clockwise path segments. In other words, the total winding number around the point must not be zero. 113 | 114 | The `even-odd` winding rule states that a point is inside the shape if the number of path segments around the point is odd. 115 | 116 | Both winding rules can be used to fill a shape, and the one that is used can be specified in the SVG code. The default winding rule for SVG is the `non-zero` winding rule so it comes up more often. 117 | 118 | ![path](images/winding1.png) 119 | 120 | When a scan ray hits a edge we need to record if the edge is going up or down. This is called winding order. 121 | 122 | ![path](images/winding2.png) 123 | 124 | If the ray is going up we +1 to winding count. 125 | 126 | ![path](images/winding3.png) 127 | 128 | If the its going down we -1. 129 | 130 | ![path](images/winding4.png) 131 | 132 | This way we can scan the whole image. And have complex interactive shapes. 133 | 134 | ![path](images/winding5.png) 135 | 136 | With `even-odd` you fill if winding count is an odd number. 137 | 138 | ![path](images/winding6.png) 139 | 140 | Paths must be defined is `non-zero` or `even-odd`. Only the simplest paths look the same with both rules, the more complex paths with holes will use one or the other rules. 141 | 142 | Here is an example of the same path that was meant to be drawn with `non-zero` but looks incorrect when drawn with `even-odd`. 143 | 144 | `Non-zero` is primary used for text. 145 | 146 | ## Caps and Joins 147 | 148 | In digital graphics, caps and joins are terms that refer to the way that strokes (lines and curves) are drawn and connected to each other. Caps refer to the way that the ends of a stroke are drawn, and joins refer to the way that two strokes are connected when they meet. 149 | 150 | Caps and joins are important because they can affect the appearance and readability of a graphic. For example, if the caps of a line are too small or too large, it can make the line difficult to see or follow. Similarly, if the joins between strokes are not smooth or well-defined, it can make a graphic look jagged or disjointed. 151 | 152 | Pixie supports three different types of caps: `ButtCap`, `RoundCap`, and `SquareCap`. The butt cap is a flat cap that extends the stroke to the end point without adding any additional shape. The round cap is a circular cap that extends the stroke by adding a half-circle at the end point. The square cap is a rectangular cap that extends the stroke by adding a square at the end point. 153 | 154 | ![path](images/caps.png) 155 | 156 | Pixie also supports three different types of joins: `MiterJoin`, `RoundJoin`, and `BevelJoin`. The miter join is a sharp, pointed join that is created by extending the strokes at their intersection point until they meet. 157 | The round join is a smooth, curved join that is created by connecting the endpoints of the strokes with a circular arc. 158 | The bevel join is a flat join that is created by connecting the endpoints of the strokes with a straight line. T 159 | 160 | The miter angle limit is used to control the maximum angle that the strokes can be extended before the miter join is replaced with aa bevel join. This can be useful because very large miter angles can create long, pointed joins that may not look aesthetically pleasing or may be difficult to read. 161 | 162 | ![path](images/joins.png) 163 | 164 | Overall, caps and joins are important features of digital graphics that can affect the appearance and readability of a graphic. By choosing the appropriate caps and joins for a given graphic, you can create smooth, well-defined lines and curves that are easy to read and follow. 165 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Pixie 3 | 4 | ![intro](images/intro1.png) 5 | 6 | Pixie is a general purpose and full-featured 2d graphics library, supporting both vector and raster operations. 7 | 8 | With Pixie you can crop images, draw shapes, apply masks, layer gradients, typeset text, load textures for OpenGL and much more. 9 | 10 | There are API bindings for Pixie available in other languages with more coming soon: [Python](https://github.com/treeform/pixie-python) 11 | 12 | In addition to Pixie's own API, Pixie also includes a Canvas API those familiar with the `` element in Javascript. 13 | 14 | Pixie is similar to [Cairo](https://www.cairographics.org/) and [Skia](https://skia.org) and is written entirely in [Nim](https://nim-lang.org/). 15 | 16 | Pixie is fast thanks to optimized algorithms and extensive SIMD using both `SSE2` and`AVX2` on x64 CPUs and `NEON` on Arm CPUs and Apple Silicon. 17 | 18 | You can use Pixie to load a variety of common raster image formats such as `.png`, `.jpeg`, `.gif`, `.bmp`, as well as some unique formats like `.qoi`, `.ppm`. Pixie also supports a subset of `.svg`. 19 | 20 | In addition to loading image formats, Pixie can also load fonts in `.ttf`, `.otf` formats, as well and less common `.svg` font format. 21 | 22 | ## Table of contents 23 | 24 | * [Chapter 1: Tutorial](tutorial.md) 25 | * [Chapter 2: Context](context.md) 26 | * [Chapter 3: Images](images.md) 27 | * [Chapter 4: Paths](paths.md) 28 | * [Chapter 5: Text](text.md) 29 | * [Chapter 6: Blending](blending.md) 30 | 31 | ## Helpful links 32 | 33 | * [Pixie on Github](https://github.com/treeform/pixie) 34 | * [API Reference](https://treeform.github.io/pixie/pixie.html) 35 | * [Nimble Directory](https://nimble.directory/pkg/pixie) 36 | * [PyPI page for pixie-python](https://pypi.org/project/pixie-python) 37 | 38 | ## Videos 39 | 40 | * [Pixie - A full-featured 2D graphics library for Nim](https://www.youtube.com/watch?v=8acDfUIwLnk) 41 | -------------------------------------------------------------------------------- /src/blending.nim: -------------------------------------------------------------------------------- 1 | # # Blending and Masking 2 | # 3 | # Pixie supports variety of blending modes and some of them can be used for masking as well. 4 | # 5 | # Starting with background image by Hulki Okan Tabak @hulkiokantabak of Bosphorus Coast, Istanbul, Turkey: 6 | # 7 | # ![blend image](images/background.png) 8 | # 9 | # Here is what blends are capable of: 10 | # 11 | # ### NormalBlend 12 | # 13 | # The default blend for everything. Just blends the colors normally. 14 | # 15 | # ![blend image](images/blendNormalBlend.png) 16 | # 17 | # ### DarkenBlend 18 | # 19 | # ![blend image](images/blendDarkenBlend.png) 20 | # 21 | # ### MultiplyBlend 22 | # 23 | # ![blend image](images/blendMultiplyBlend.png) 24 | # 25 | # ### ColorBurnBlend 26 | # 27 | # ![blend image](images/blendColorBurnBlend.png) 28 | # 29 | # ### LightenBlend 30 | # 31 | # ![blend image](images/blendLightenBlend.png) 32 | # 33 | # ### ScreenBlend 34 | # 35 | # ![blend image](images/blendScreenBlend.png) 36 | # 37 | # ### ColorDodgeBlend 38 | # 39 | # ![blend image](images/blendColorDodgeBlend.png) 40 | # 41 | # ### OverlayBlend 42 | # 43 | # ![blend image](images/blendOverlayBlend.png) 44 | # 45 | # ### SoftLightBlend 46 | # 47 | # ![blend image](images/blendSoftLightBlend.png) 48 | # 49 | # ### HardLightBlend 50 | # 51 | # ![blend image](images/blendHardLightBlend.png) 52 | # 53 | # ### DifferenceBlend 54 | # 55 | # ![blend image](images/blendDifferenceBlend.png) 56 | # 57 | # ### ExclusionBlend 58 | # 59 | # ![blend image](images/blendExclusionBlend.png) 60 | # 61 | # ### HueBlend 62 | # 63 | # ![blend image](images/blendHueBlend.png) 64 | # 65 | # ### SaturationBlend 66 | # 67 | # ![blend image](images/blendSaturationBlend.png) 68 | # 69 | # ### ColorBlend 70 | # 71 | # ![blend image](images/blendColorBlend.png) 72 | # 73 | # ### LuminosityBlend 74 | # 75 | # ![blend image](images/blendLuminosityBlend.png) 76 | # 77 | # ## Masking 78 | # 79 | # Masking is just a special blend mode. 80 | # 81 | # ### Use MaskBlend to simulate a intersection of masks. 82 | # 83 | # MaskBlend is the default blend for masking operations. 84 | # 85 | # ![blend image](images/bool_intersect.png) 86 | # 87 | # ### Use NormalBlend to simulate a union of masks. 88 | # 89 | # ![blend image](images/bool_union.png) 90 | # 91 | # ### Use SubtractMaskBlend to simulate a subtraction of masks. 92 | # 93 | # ![blend image](images/bool_subtract.png) 94 | # 95 | # ### Use ExcludeMaskBlend to simulate a exclusion of masks. 96 | # 97 | # ![blend image](images/bool_exclude.png) 98 | # 99 | # 114 | # 115 | # ## OverwriteBlend 116 | # 117 | # Overwrite blend is special in that it copies the data from one image into another as fast as possible. 118 | # 119 | # This blend is only safe to use on to an image you know is empty. Usually an image that was just created. 120 | # Otherwise odd artifacts will happen. 121 | # 122 | # You can replace NormalBlend with OverwriteBlend if you know that the image you are drawing to is empty. 123 | # Using OverwriteBlend here is faster as it does not care what 124 | # pixies background has: 125 | # 126 | import pixie 127 | let 128 | background = newImage(100, 100) 129 | profile = readImage("images/circleProfile.png") 130 | background.draw(profile, blendMode = OverwriteBlend) 131 | background.writeFile("images/overwriteBlend.png") 132 | # 133 | # ![blend image](images/overwriteBlend.png) 134 | # 135 | -------------------------------------------------------------------------------- /src/context.nim: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /src/extract.nim: -------------------------------------------------------------------------------- 1 | import strutils 2 | 3 | proc extract(fileName: string) = 4 | ## Extracts code from md files. 5 | 6 | var 7 | md = readFile(fileName) 8 | code = "" 9 | inCode = false 10 | 11 | for line in md.split("\n"): 12 | if line.strip() == "```nim": 13 | inCode = true 14 | continue 15 | if line.strip() == "```": 16 | inCode = false 17 | continue 18 | 19 | if inCode: 20 | code.add line 21 | code.add "\n" 22 | else: 23 | code.add "# " 24 | code.add line 25 | code.add "\n" 26 | 27 | writeFile("src/" & fileName.replace(".md", ".nim"), code) 28 | 29 | extract("tutorial.md") 30 | extract("context.md") 31 | extract("blending.md") 32 | -------------------------------------------------------------------------------- /src/genblends.nim: -------------------------------------------------------------------------------- 1 | import pixie 2 | 3 | 4 | let 5 | background = readImage("images/background.png") 6 | grays = readImage("images/grays.png") 7 | hues = readImage("images/hues.png") 8 | masks = readImage("images/masks.png") 9 | checkers = readImage("images/checkers.png") 10 | 11 | const blends = @[ 12 | NormalBlend, 13 | DarkenBlend, 14 | MultiplyBlend, 15 | ColorBurnBlend, 16 | LightenBlend, 17 | ScreenBlend, 18 | ColorDodgeBlend, 19 | OverlayBlend, 20 | SoftLightBlend, 21 | HardLightBlend, 22 | DifferenceBlend, 23 | ExclusionBlend, 24 | HueBlend, 25 | SaturationBlend, 26 | ColorBlend, 27 | LuminosityBlend 28 | ] 29 | 30 | for blend in blends: 31 | let 32 | image = newImage(background.width * 2, background.height) 33 | t = translate(vec2(background.width.float32, 0)) 34 | 35 | image.draw(background) 36 | image.draw(background, t) 37 | 38 | image.draw(grays, blendMode = blend) 39 | image.draw(hues, t, blendMode = blend) 40 | 41 | image.writeFile("images/blend" & $blend & ".png") 42 | 43 | const maskBlends = @[ 44 | MaskBlend, 45 | OverwriteBlend, 46 | SubtractMaskBlend, 47 | ExcludeMaskBlend 48 | ] 49 | 50 | for blend in maskBlends: 51 | let 52 | image = newImage(background.width, background.height) 53 | sub = newImage(background.width, background.height) 54 | 55 | sub.draw(background) 56 | sub.draw(masks, blendMode = blend) 57 | 58 | image.draw(checkers) 59 | image.draw(sub) 60 | 61 | image.writeFile("images/blend" & $blend & ".png") 62 | -------------------------------------------------------------------------------- /src/tutorial.nim: -------------------------------------------------------------------------------- 1 | # # Pixie tutorial 2 | # 3 | # So you came to the right place, here I will help you out with how with pixie basics and how to use it in your projects. 4 | # 5 | # Are you looking for the API reference? https://nimdocs.com/treeform/pixie/pixie.html 6 | # 7 | # ## Installing pixie. 8 | # 9 | # ## Nim 10 | # 11 | # * Nimble https://nimble.directory/pkg/pixie 12 | # * Github http://github.com/treeform/pixie 13 | # 14 | # ```sh 15 | # nimble install pixie 16 | # 17 | # Then just import it: 18 | # 19 | import pixie 20 | # 21 | # ### Python 22 | # 23 | # * Pypi https://pypi.org/project/pixie-python 24 | # * Github http://github.com/treeform/pixie-python 25 | # 26 | # ```sh 27 | # pip install pixie-python 28 | # 29 | # Then just import it: 30 | # 31 | # ```python 32 | # import pixie 33 | # 34 | # or 35 | # 36 | # ```python 37 | # from pixie import * 38 | # 39 | # 48 | # 49 | # ## Drawing paths 50 | # 51 | # You can either load paths from a standard SVG style string or build it up with methods. 52 | # 53 | var heart1 = newImage(200, 200) 54 | heart1.fill(rgba(255, 255, 255, 255)) 55 | 56 | heart1.fillPath( 57 | """ 58 | M 20 60 59 | A 40 40 90 0 1 100 60 60 | A 40 40 90 0 1 180 60 61 | Q 180 120 100 180 62 | Q 20 120 20 60 63 | z 64 | """, 65 | "#FC427B" 66 | ) 67 | 68 | heart1.writeFile("images/heart1.png") 69 | # 70 | # Will produce: 71 | # 72 | # ![example output](images/heart1.png) 73 | # 74 | # But you can also build the path with path commands: 75 | # 76 | # 77 | var heart2 = newImage(200, 200) 78 | heart2.fill(rgba(255, 255, 255, 255)) 79 | 80 | var heartPath = newPath() 81 | heartPath.moveTo(20, 60) 82 | heartPath.ellipticalArcTo(40, 40, 90, false, true, 100, 60) 83 | heartPath.ellipticalArcTo(40, 40, 90, false, true, 180, 60) 84 | heartPath.quadraticCurveTo(180, 120, 100, 180) 85 | heartPath.quadraticCurveTo(20, 120, 20, 60) 86 | heartPath.closePath() 87 | 88 | heart2.fillPath(heartPath, "#FC427B") 89 | heart2.writeFile("images/heart2.png") 90 | # 91 | # Both methods will produce the same image: 92 | # 93 | # ![example output](images/heart2.png) 94 | # 95 | # You can use paths to draw almost anything. 96 | # 97 | # ## Gradients 98 | # 99 | var heart3 = newImage(200, 200) 100 | heart3.fill(rgba(255, 255, 255, 255)) 101 | 102 | let gradientPaint = newPaint(RadialGradientPaint) 103 | gradientPaint.gradientHandlePositions = @[ 104 | vec2(100, 100), 105 | vec2(200, 100), 106 | vec2(100, 200) 107 | ] 108 | gradientPaint.gradientStops = @[ 109 | ColorStop(color: color(1, 0, 0, 1), position: 0), 110 | ColorStop(color: color(1, 0, 0, 0.15), position: 1.0), 111 | ] 112 | 113 | heart3.fillPath( 114 | """ 115 | M 20 60 116 | A 40 40 90 0 1 100 60 117 | A 40 40 90 0 1 180 60 118 | Q 180 120 100 180 119 | Q 20 120 20 60 120 | z 121 | """, 122 | gradientPaint 123 | ) 124 | 125 | heart3.writeFile("images/heart3.png") 126 | # 127 | # ![example output](images/heart3.png) 128 | # 129 | # ## Masking 130 | # 131 | # Pixie supports many different blends and masking modes. You can use masking blends modes to mask out areas of the image. Here we will draw a background "X" then cut the heart path out of it. 132 | # 133 | var background = newImage(200, 200) 134 | 135 | background.fill("#3498db") 136 | 137 | background.strokePath( 138 | """ 139 | M 25, 25 140 | L 175, 175 141 | M 25, 175 142 | L 175, 25 143 | """, 144 | paint = "#ecf0f1", 145 | strokeWidth = 30 146 | ) 147 | 148 | background.writeFile("images/masking1.png") 149 | # 150 | # You can see this produces crossed lines: 151 | # 152 | # ![example output](images/masking1.png) 153 | # 154 | # Mask paint is a special type of paint that has `blendMod` set to `MaskBlend`. 155 | # Instead of adding color to the image the `MaskBlend` will eat color a way: 156 | # 157 | var maskPaint = newPaint(SolidPaint) 158 | maskPaint.color = color(1, 1, 1, 1) 159 | maskPaint.blendMode = MaskBlend 160 | # 161 | # And now you can just draw the path normally but with the new mask paint: 162 | # 163 | var mask = newImage(200, 200) 164 | background.fillPath( 165 | """ 166 | M 20 60 167 | A 40 40 90 0 1 100 60 168 | A 40 40 90 0 1 180 60 169 | Q 180 120 100 180 170 | Q 20 120 20 60 171 | z 172 | """, 173 | maskPaint 174 | ) 175 | 176 | background.writeFile("images/masking2.png") 177 | # 178 | # ![example output](images/masking2.png) 179 | # 180 | # This results in a cutout "X" image. 181 | # 182 | # ## Drawing text 183 | # 184 | # Pixie has many powerful text features. 185 | # 186 | # You can typeset text in different ways: 187 | # 188 | var textImage = newImage(240, 70) 189 | textImage.fill("#FFFFFF") 190 | var font = readFont("fonts/Roboto-Regular_1.ttf") 191 | font.size = 40 192 | 193 | let text = "Hello world." 194 | 195 | textImage.fillText(font.typeset(text, vec2(240, 60)), translate(vec2(10, 10))) 196 | textImage.writeFile("images/text1.png") 197 | # ![example output](images/text1.png) 198 | # 199 | # Text is layed out in spans and spans are layed out into an arrangement. You can create different spans of text of different fonts and sizes. 200 | # 201 | textImage = newImage(200, 200) 202 | let typeface = readTypeface("fonts/Ubuntu-Regular_1.ttf") 203 | 204 | proc newFont(typeface: Typeface, size: float32, color: Color): Font = 205 | result = newFont(typeface) 206 | result.size = size 207 | result.paint.color = color 208 | 209 | let spans = @[ 210 | newSpan("verb [with object] ", 211 | newFont(typeface, 12, color(0.6, 0.6, 0.6, 1))), 212 | newSpan("strallow\n", newFont(typeface, 36, color(0, 0, 0, 1))), 213 | newSpan("\nstral·low\n", newFont(typeface, 13, color(0, 0.5, 0.953125, 1))), 214 | newSpan("\n1. free (something) from restrictive restrictions \"the regulations are intended to strallow changes in public policy\" ", 215 | newFont(typeface, 14, color(0.3125, 0.3125, 0.3125, 1))) 216 | ] 217 | 218 | textImage.fill("#FFFFFF") 219 | textImage.fillText(typeset(spans, vec2(180, 180)), translate(vec2(10, 10))) 220 | textImage.writeFile("images/text2.png") 221 | # 222 | # ![example output](images/text2.png) 223 | # 224 | # ### Supported features 225 | # 226 | # Currently pixie supports many text features but not all of them: 227 | # 228 | # | Description | Supported 229 | # | ------------------------------- | --------- 230 | # | ASCII English | ✅ 231 | # | European Alphabets | ✅ 232 | # | Other Alphabets | ✅ 233 | # | CJK - Chinese/Japanese/Korean | ✅ 234 | # | Cursive Fonts | some 235 | # | Emoji | no 236 | # | RTL - Arabic/Hebrew | no 237 | # | Unicode shaping | no 238 | # | Ligatures | no 239 | # 240 | # ## For websites 241 | # 242 | # Do you have a server that needs to crop or scale images? You can use uploaded file size and `image.readImageDimensions()` to first make sure its an image you want to load: 243 | # 244 | let size = readImageDimensions("images/profile.jpeg") 245 | echo size 246 | if size.width > 512 or size.height > 512: 247 | quit("Image dimensions too big") 248 | # 249 | # Now that we know its not to big to cause memory issues we can read and crop it: 250 | # 251 | var profile = readImage("images/profile.jpeg") 252 | var smallProfile = profile.resize(64, 64) 253 | smallProfile.writeFile("images/smallProfile.png") 254 | # 255 | # We made a bigger image: 256 | # 257 | # ![example output](images/profile.jpeg) 258 | # 259 | # Turn into a 64x64 profile image: 260 | # 261 | # ![example output](images/smallProfile.png) 262 | # 263 | # You can also cut people out of a circle for those new "modern profiles images". Simply mask some thing with a circle like this: 264 | # 265 | var maskPath = newPath() 266 | maskPath.circle(32, 32, 32) 267 | smallProfile.fillPath(maskPath, maskPaint) 268 | # Then just write it out: 269 | smallProfile.writeFile("images/circleProfile.png") 270 | # ![example output](images/circleProfile.png) 271 | # 272 | # Some times you don't have a person's photo and want to just create a colored circle with the letter. 273 | # First we will draw the background circle: 274 | # 275 | var letterProfile = newImage(64, 64) 276 | var path = newPath() 277 | path.circle(32, 32, 32) 278 | var paint = newPaint(SolidPaint) 279 | paint.color = parseHtmlColor("#2ecc71") 280 | letterProfile.fillPath(path, paint) 281 | 282 | letterProfile.writeFile("images/letterProfile1.png") 283 | # 284 | # That looks like this: 285 | # 286 | # ![example output](images/letterProfile1.png) 287 | # 288 | # Then we will draw the foreground letter: 289 | # 290 | var profileFont = readFont("fonts/Roboto-Regular_1.ttf") 291 | profileFont.size = 36 292 | profileFont.paint = "#ecf0f1" 293 | let profileLetters = "Ev" 294 | letterProfile.fillText(profileFont.typeset( 295 | profileLetters, 296 | vec2(64, 64), 297 | hAlign = CenterAlign, 298 | vAlign = MiddleAlign 299 | )) 300 | # 301 | # And this is what you get: 302 | # 303 | letterProfile.writeFile("images/letterProfile2.png") 304 | # 305 | # ![example output](images/letterProfile2.png) 306 | # 307 | # ## For games and openGL 308 | # 309 | # You can load an image and convert it to a texture like this: 310 | # 311 | import opengl 312 | 313 | proc loadTexture() = 314 | var textureImage = readImage("images/testTexture.png") 315 | echo textureImage.width, " by ", textureImage.height 316 | 317 | # pointer to image pixels -> textureImage.data[0].addr 318 | 319 | var textureId: GLuint 320 | glGenTextures(1, textureId.addr) 321 | 322 | glBindTexture(GL_TEXTURE_2D, textureId) 323 | glTexImage2D( 324 | target = GL_TEXTURE_2D, 325 | level = 0, 326 | internalFormat = GL_RGBA8.GLint, 327 | width = textureImage.width.GLsizei, 328 | height = textureImage.height.GLsizei, 329 | border = 0, 330 | format = GL_RGBA, 331 | `type` = GL_UNSIGNED_BYTE, 332 | pixels = cast[pointer](textureImage.data[0].addr) 333 | ) 334 | # 335 | # ![example output](images/testTexture.png) 336 | # 337 | # > Keep in mind that pixie images are in premultiplied alpha state. We recommend doing all alpha blending in premultiplied alpha for 2d and 3d graphics. 338 | # 339 | -------------------------------------------------------------------------------- /text.md: -------------------------------------------------------------------------------- 1 | 2 | # Text 3 | 4 | Pixie is a library that is designed to provide a simple and efficient way to draw text on screen. It is intended to be used as a replacement for other text rendering libraries such as FreeType, HarfBuzz, and Pango, which are commonly used in graphics applications and operating systems. 5 | 6 | One of the main goals of Pixie is to provide a unified system for drawing text, which means that it can be used to render text in a consistent way across different platforms and devices. This can make it easier for developers to create consistent applications that display text correctly on different devices and operating systems. 7 | 8 | Pixie is designed to be lightweight and easy to use, and it offers a number of features that make it a useful tool for text rendering. For example, it supports a wide range of font formats and can handle complex text layouts. It also includes support for advanced typography features like kerning. 9 | 10 | ![path](images/textIsHard.png) 11 | 12 | Rendering text on screen can be a challenging task because there are so many different languages and writing modes to consider. Each language has its own unique set of characters, and different languages have different rules for how those characters should be displayed and positioned. This can make it difficult to create a system that can handle text rendering for all languages in a consistent and accurate way. 13 | 14 | But at its basic level text is just a collection of glyphs (letters) and glyphs are just paths. 15 | 16 | ![path](images/textLayout.png) 17 | 18 | Each glyph has a set of metrics that you can query from the typeface. 19 | The metrics explain how a glyphs should fit with the other glyphs. 20 | 21 | ![path](images/textMetrics.png) 22 | 23 | Its very important to get kerning correct. 24 | You can see how obviously bad incorrect kerning looks like: 25 | 26 | ![path](images/textKerning1.png) 27 | 28 | The kerning refers to distance or more properly the adjustment of the default distance between the letters. 29 | Kerning can be negative or positive. 30 | 31 | ![path](images/textKerning2.png) 32 | 33 | Its important to get text to be kerned correctly. 34 | 35 | ![path](images/textKerning3.png) 36 | 37 | 38 | ## Spans 39 | 40 | In Pixie, a `Span` is a contiguous block of text with the same font, size, and paint color. A `Span` is used to apply a consistent set of formatting options to a range of text within a document or other text-based content. 41 | 42 | You can layout any number of spans following each other in order to create complex layouts. This can be useful for creating documents with multiple fonts, sizes, and colors. 43 | 44 | ![path](images/textSpans.png) 45 | 46 | It is generally recommended to end a span on a space or a newline because there is typically no kerning between letters of different fonts and sizes. Without kerning, the spacing between letters of different fonts and sizes may not be consistent, which can make text difficult to read. By ending a span on a space or newline, you can ensure that the spacing between letters is consistent and that the text is easy to read. 47 | -------------------------------------------------------------------------------- /tutorial.md: -------------------------------------------------------------------------------- 1 | # Pixie tutorial 2 | 3 | So you came to the right place, here I will help you out with how with pixie basics and how to use it in your projects. 4 | 5 | Are you looking for the API reference? https://treeform.github.io/pixie/pixie.html 6 | 7 | ## Installing pixie. 8 | 9 | ## Nim 10 | 11 | * Nimble https://nimble.directory/pkg/pixie 12 | * Github http://github.com/treeform/pixie 13 | 14 | ```sh 15 | nimble install pixie 16 | ``` 17 | 18 | Then just import it: 19 | 20 | ```nim 21 | import pixie 22 | ``` 23 | 24 | ### Python 25 | 26 | * Pypi https://pypi.org/project/pixie-python 27 | * Github http://github.com/treeform/pixie-python 28 | 29 | ```sh 30 | pip install pixie-python 31 | ``` 32 | 33 | Then just import it: 34 | 35 | ```python 36 | import pixie 37 | ``` 38 | 39 | or 40 | 41 | ```python 42 | from pixie import * 43 | ``` 44 | 45 | 55 | 56 | ## Drawing paths 57 | 58 | You can either load paths from a standard SVG style string or build it up with methods. 59 | 60 | ```nim 61 | var heart1 = newImage(200, 200) 62 | heart1.fill(rgba(255, 255, 255, 255)) 63 | 64 | heart1.fillPath( 65 | """ 66 | M 20 60 67 | A 40 40 90 0 1 100 60 68 | A 40 40 90 0 1 180 60 69 | Q 180 120 100 180 70 | Q 20 120 20 60 71 | z 72 | """, 73 | "#FC427B" 74 | ) 75 | 76 | heart1.writeFile("images/heart1.png") 77 | ``` 78 | 79 | Will produce: 80 | 81 | ![example output](images/heart1.png) 82 | 83 | But you can also build the path with path commands: 84 | 85 | 86 | ```nim 87 | var heart2 = newImage(200, 200) 88 | heart2.fill(rgba(255, 255, 255, 255)) 89 | 90 | var heartPath = newPath() 91 | heartPath.moveTo(20, 60) 92 | heartPath.ellipticalArcTo(40, 40, 90, false, true, 100, 60) 93 | heartPath.ellipticalArcTo(40, 40, 90, false, true, 180, 60) 94 | heartPath.quadraticCurveTo(180, 120, 100, 180) 95 | heartPath.quadraticCurveTo(20, 120, 20, 60) 96 | heartPath.closePath() 97 | 98 | heart2.fillPath(heartPath, "#FC427B") 99 | heart2.writeFile("images/heart2.png") 100 | ``` 101 | 102 | Both methods will produce the same image: 103 | 104 | ![example output](images/heart2.png) 105 | 106 | You can use paths to draw almost anything. 107 | 108 | ## Gradients 109 | 110 | ```nim 111 | var heart3 = newImage(200, 200) 112 | heart3.fill(rgba(255, 255, 255, 255)) 113 | 114 | let gradientPaint = newPaint(RadialGradientPaint) 115 | gradientPaint.gradientHandlePositions = @[ 116 | vec2(100, 100), 117 | vec2(200, 100), 118 | vec2(100, 200) 119 | ] 120 | gradientPaint.gradientStops = @[ 121 | ColorStop(color: color(1, 0, 0, 1), position: 0), 122 | ColorStop(color: color(1, 0, 0, 0.15), position: 1.0), 123 | ] 124 | 125 | heart3.fillPath( 126 | """ 127 | M 20 60 128 | A 40 40 90 0 1 100 60 129 | A 40 40 90 0 1 180 60 130 | Q 180 120 100 180 131 | Q 20 120 20 60 132 | z 133 | """, 134 | gradientPaint 135 | ) 136 | 137 | heart3.writeFile("images/heart3.png") 138 | ``` 139 | 140 | ![example output](images/heart3.png) 141 | 142 | ## Masking 143 | 144 | Pixie supports many different blends and masking modes. You can use masking blends modes to mask out areas of the image. Here we will draw a background "X" then cut the heart path out of it. 145 | 146 | ```nim 147 | var background = newImage(200, 200) 148 | 149 | background.fill("#3498db") 150 | 151 | background.strokePath( 152 | """ 153 | M 25, 25 154 | L 175, 175 155 | M 25, 175 156 | L 175, 25 157 | """, 158 | paint = "#ecf0f1", 159 | strokeWidth = 30 160 | ) 161 | 162 | background.writeFile("images/masking1.png") 163 | ``` 164 | 165 | You can see this produces crossed lines: 166 | 167 | ![example output](images/masking1.png) 168 | 169 | Mask paint is a special type of paint that has `blendMod` set to `MaskBlend`. 170 | Instead of adding color to the image the `MaskBlend` will eat color a way: 171 | 172 | ```nim 173 | var maskPaint = newPaint(SolidPaint) 174 | maskPaint.color = color(1, 1, 1, 1) 175 | maskPaint.blendMode = MaskBlend 176 | ``` 177 | 178 | And now you can just draw the path normally but with the new mask paint: 179 | 180 | ```nim 181 | var mask = newImage(200, 200) 182 | background.fillPath( 183 | """ 184 | M 20 60 185 | A 40 40 90 0 1 100 60 186 | A 40 40 90 0 1 180 60 187 | Q 180 120 100 180 188 | Q 20 120 20 60 189 | z 190 | """, 191 | maskPaint 192 | ) 193 | 194 | background.writeFile("images/masking2.png") 195 | ``` 196 | 197 | ![example output](images/masking2.png) 198 | 199 | This results in a cutout "X" image. 200 | 201 | ## Drawing text 202 | 203 | Pixie has many powerful text features. 204 | 205 | You can typeset text in different ways: 206 | 207 | ```nim 208 | var textImage = newImage(240, 70) 209 | textImage.fill("#FFFFFF") 210 | var font = readFont("fonts/Roboto-Regular_1.ttf") 211 | font.size = 40 212 | 213 | let text = "Hello world." 214 | 215 | textImage.fillText(font.typeset(text, vec2(240, 60)), translate(vec2(10, 10))) 216 | textImage.writeFile("images/text1.png") 217 | ``` 218 | ![example output](images/text1.png) 219 | 220 | Text is layed out in spans and spans are layed out into an arrangement. You can create different spans of text of different fonts and sizes. 221 | 222 | ```nim 223 | var textImage = newImage(200, 200) 224 | let typeface = readTypeface("fonts/Ubuntu-Regular_1.ttf") 225 | 226 | proc newFont(typeface: Typeface, size: float32, color: Color): Font = 227 | result = newFont(typeface) 228 | result.size = size 229 | result.paint.color = color 230 | 231 | let spans = @[ 232 | newSpan("verb [with object] ", 233 | newFont(typeface, 12, color(0.6, 0.6, 0.6, 1))), 234 | newSpan("strallow\n", newFont(typeface, 36, color(0, 0, 0, 1))), 235 | newSpan("\nstral·low\n", newFont(typeface, 13, color(0, 0.5, 0.953125, 1))), 236 | newSpan("\n1. free (something) from restrictive restrictions \"the regulations are intended to strallow changes in public policy\" ", 237 | newFont(typeface, 14, color(0.3125, 0.3125, 0.3125, 1))) 238 | ] 239 | 240 | textImage.fill("#FFFFFF") 241 | textImage.fillText(typeset(spans, vec2(180, 180)), translate(vec2(10, 10))) 242 | textImage.writeFile("images/text2.png") 243 | ``` 244 | 245 | ![example output](images/text2.png) 246 | 247 | ### Supported features 248 | 249 | Currently pixie supports many text features but not all of them: 250 | 251 | | Description | Supported 252 | | ------------------------------- | --------- 253 | | ASCII English | ✅ 254 | | European Alphabets | ✅ 255 | | Other Alphabets | ✅ 256 | | CJK - Chinese/Japanese/Korean | ✅ 257 | | Cursive Fonts | some 258 | | Emoji | no 259 | | RTL - Arabic/Hebrew | no 260 | | Unicode shaping | no 261 | | Ligatures | no 262 | 263 | ## For websites 264 | 265 | Do you have a server that needs to crop or scale images? You can use uploaded file size and `image.readImageDimensions()` to first make sure its an image you want to load: 266 | 267 | ```nim 268 | let size = readImageDimensions("images/profile.jpeg") 269 | echo size 270 | if size.width > 512 or size.height > 512: 271 | quit("Image dimensions too big") 272 | ``` 273 | 274 | Now that we know its not to big to cause memory issues we can read and crop it: 275 | 276 | ```nim 277 | var profile = readImage("images/profile.jpeg") 278 | var smallProfile = profile.resize(64, 64) 279 | smallProfile.writeFile("images/smallProfile.png") 280 | ``` 281 | 282 | We made a bigger image: 283 | 284 | ![example output](images/profile.jpeg) 285 | 286 | Turn into a 64x64 profile image: 287 | 288 | ![example output](images/smallProfile.png) 289 | 290 | You can also cut people out of a circle for those new "modern profiles images". Simply mask some thing with a circle like this: 291 | 292 | ```nim 293 | var maskPath = newPath() 294 | maskPath.circle(32, 32, 32) 295 | smallProfile.fillPath(maskPath, maskPaint) 296 | ``` 297 | Then just write it out: 298 | ```nim 299 | smallProfile.writeFile("images/circleProfile.png") 300 | ``` 301 | ![example output](images/circleProfile.png) 302 | 303 | Some times you don't have a person's photo and want to just create a colored circle with the letter. 304 | First we will draw the background circle: 305 | 306 | ```nim 307 | var letterProfile = newImage(64, 64) 308 | var path = newPath() 309 | path.circle(32, 32, 32) 310 | var paint = newPaint(SolidPaint) 311 | paint.color = parseHtmlColor("#2ecc71") 312 | letterProfile.fillPath(path, paint) 313 | 314 | letterProfile.writeFile("images/letterProfile1.png") 315 | ``` 316 | 317 | That looks like this: 318 | 319 | ![example output](images/letterProfile1.png) 320 | 321 | Then we will draw the foreground letter: 322 | 323 | ```nim 324 | var profileFont = readFont("fonts/Roboto-Regular_1.ttf") 325 | profileFont.size = 36 326 | profileFont.paint = "#ecf0f1" 327 | let profileLetters = "Ev" 328 | letterProfile.fillText(profileFont.typeset( 329 | profileLetters, 330 | vec2(64, 64), 331 | hAlign = CenterAlign, 332 | vAlign = MiddleAlign 333 | )) 334 | ``` 335 | 336 | And this is what you get: 337 | 338 | ```nim 339 | letterProfile.writeFile("images/letterProfile2.png") 340 | ``` 341 | 342 | ![example output](images/letterProfile2.png) 343 | 344 | ## For games and openGL 345 | 346 | You can load an image and convert it to a texture like this: 347 | 348 | ```nim 349 | import opengl 350 | 351 | proc loadTexture() = 352 | let testTexture = readImage("examples/data/testTexture.png") 353 | 354 | var textureId: uint32 355 | glGenTextures(1, textureId.addr) 356 | glBindTexture(GL_TEXTURE_2D, textureId) 357 | glTexImage2D( 358 | target = GL_TEXTURE_2D, 359 | level = 0, 360 | internalFormat = GL_RGBA8.GLint, 361 | width = testTexture.width.GLsizei, 362 | height = testTexture.height.GLsizei, 363 | border = 0, 364 | format = GL_RGBA, 365 | `type` = GL_UNSIGNED_BYTE, 366 | pixels = cast[pointer](testTexture.data[0].addr) 367 | ) 368 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) 369 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) 370 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP) 371 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP) 372 | ``` 373 | 374 | ![example output](images/testTexture.png) 375 | 376 | See the complete working example: https://github.com/treeform/windy/blob/master/examples/textured_quad.nim 377 | 378 | 379 | > Keep in mind that pixie images are in premultiplied alpha state. 380 | > We recommend doing all alpha blending in premultiplied alpha for 2d and 3d graphics. 381 | --------------------------------------------------------------------------------