diff --git a/canvas/README.md b/canvas/README.md new file mode 100644 index 0000000000000000000000000000000000000000..71fe4b305487c700369a8b11166a00486b65abbf --- /dev/null +++ b/canvas/README.md @@ -0,0 +1,89 @@ +## gosexy/canvas + +``gosexy/canvas`` is an image processing library based on ImageMagick's MagickWand, for the Go programming language. + +## Requeriments + +### Mac OSX + +The ImageMagick's header files are required. If you're using ``brew`` the installation is straightforward. + + $ brew install imagemagick + +### Debian + +Debian has an old version of MagickWand, in order to install gocanvas we need to install the old version and then upgrade it. + +Getting the old version of MagickWand along all its dependencies. + + $ sudo aptitude install libmagickwand-dev + +Installing a newer version of ImageMagick over the old files. + + $ sudo su + # cd /usr/local/src + # wget http://www.imagemagick.org/download/ImageMagick.tar.gz + # tar xvzf ImageMagick.tar.gz + # cd ImageMagick-6.x.y + # ./configure --prefix=/usr + # make + # make install + +### Arch Linux + +Arch Linux already has a recent version of MagickWand. + + $ sudo pacman -S extra/imagemagick + +### Windows + +Choose your [favorite binary](http://imagemagick.com/script/binary-releases.php#windows) and try it. + +### Other OS + +Please, follow the [install from source](http://imagemagick.com/script/install-source.php?ImageMagick=9uv1bcgofrv21mhftmlk4v1465) tutorial. + +## Installation + +After installing ImageMagick's header files, pull gocanvas from github: + + $ go get github.com/xiam/gosexy/canvas + +## Updating + +After installing, you can use `go get -u github.com/xiam/gosexy/canvas` to keep up to date. + +## Usage + + package main + + import "github.com/xiam/gosexy/canvas" + + func main() { + cv := canvas.New() + defer cv.Destroy() + + // Opening some image from disk. + opened := cv.Open("examples/input/example.png") + + if opened { + + // Photo auto orientation based on EXIF tags. + canvas.AutoOrientate() + + // Creating a squared thumbnail + canvas.Thumbnail(100, 100) + + // Saving the thumbnail to disk. + canvas.Write("examples/output/example-thumbnail.png") + + } + } + +## Documentation + +You can read ``gosexy/canvas`` documentation from a terminal + + $ go doc github.com/xiam/gosexy/canvas + +Or you can [browse it](http://go.pkgdoc.org/github.com/xiam/gosexy/canvas) online. diff --git a/canvas/canvas.go b/canvas/canvas.go new file mode 100644 index 0000000000000000000000000000000000000000..024c8a748f2005e2663ccc2e51ae85357d479417 --- /dev/null +++ b/canvas/canvas.go @@ -0,0 +1,637 @@ +/* + Copyright (c) 2012 JosĂŠ Carlos Nieto, http://xiam.menteslibres.org/ + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package canvas + +/* +#cgo LDFLAGS: -lMagickWand -lMagickCore +#cgo CFLAGS: -fopenmp -I/usr/include/ImageMagick + +#include <wand/magick_wand.h> + +char *MagickGetPropertyName(char **properties, size_t index) { + return properties[index]; +} +*/ +import "C" + +import "math" + +import "fmt" + +import "unsafe" + +import "strings" + +import "strconv" + +var ( + STROKE_BUTT_CAP = uint(C.ButtCap) + STROKE_ROUND_CAP = uint(C.RoundCap) + STROKE_SQUARE_CAP = uint(C.SquareCap) + + STROKE_MITER_JOIN = uint(C.MiterJoin) + STROKE_ROUND_JOIN = uint(C.RoundJoin) + STROKE_BEVEL_JOIN = uint(C.BevelJoin) + + FILL_EVEN_ODD_RULE = uint(C.EvenOddRule) + FILL_NON_ZERO_RULE = uint(C.NonZeroRule) + + RAD_TO_DEG = 180 / math.Pi + DEG_TO_RAD = math.Pi / 180 + + UNDEFINED_ORIENTATION = uint(C.UndefinedOrientation) + TOP_LEFT_ORIENTATION = uint(C.TopLeftOrientation) + TOP_RIGHT_ORIENTATION = uint(C.TopRightOrientation) + BOTTOM_RIGHT_ORIENTATION = uint(C.BottomRightOrientation) + BOTTOM_LEFT_ORIENTATION = uint(C.BottomLeftOrientation) + LEFT_TOP_ORIENTATION = uint(C.LeftTopOrientation) + RIGHT_TOP_ORIENTATION = uint(C.RightTopOrientation) + RIGHT_BOTTOM_ORIENTATION = uint(C.RightBottomOrientation) + LEFT_BOTTOM_ORIENTATION = uint(C.LeftBottomOrientation) +) + +type Canvas struct { + wand *C.MagickWand + + fg *C.PixelWand + bg *C.PixelWand + + drawing *C.DrawingWand + + fill *C.PixelWand + stroke *C.PixelWand + + filename string + width string + height string +} + +// Private: returns wand's hexadecimal color. +func getPixelHexColor(p *C.PixelWand) string { + var rgb [3]float64 + + rgb[0] = float64(C.PixelGetRed(p)) + rgb[1] = float64(C.PixelGetGreen(p)) + rgb[2] = float64(C.PixelGetBlue(p)) + + return fmt.Sprintf("#%02x%02x%02x", int(rgb[0]*255.0), int(rgb[1]*255.0), int(rgb[2]*255.0)) +} + +// Private: returns MagickTrue or MagickFalse +func magickBoolean(value bool) C.MagickBooleanType { + if value == true { + return C.MagickTrue + } + return C.MagickFalse +} + +// Initializes the canvas environment. +func (cv Canvas) Init() { + C.MagickWandGenesis() +} + +// Opens an image file, returns true on success. +func (cv Canvas) Open(filename string) bool { + cv.filename = filename + status := C.MagickReadImage(cv.wand, C.CString(cv.filename)) + if status == C.MagickFalse { + return false + } + return true +} + +// Auto-orientates canvas based on its original image's EXIF metadata +func (cv Canvas) AutoOrientate() bool { + + data := cv.Metadata() + + orientation, err := strconv.Atoi(data["exif:Orientation"]) + + if err != nil { + return false + } + + switch uint(orientation) { + case TOP_LEFT_ORIENTATION: + // normal + + case TOP_RIGHT_ORIENTATION: + cv.Flop() + + case BOTTOM_RIGHT_ORIENTATION: + cv.RotateCanvas(math.Pi) + + case BOTTOM_LEFT_ORIENTATION: + cv.Flip() + + case LEFT_TOP_ORIENTATION: + cv.Flip() + cv.RotateCanvas(-math.Pi / 2) + + case RIGHT_TOP_ORIENTATION: + cv.RotateCanvas(-math.Pi / 2) + + case RIGHT_BOTTOM_ORIENTATION: + cv.Flop() + cv.RotateCanvas(-math.Pi / 2) + + case LEFT_BOTTOM_ORIENTATION: + cv.RotateCanvas(math.Pi / 2) + + default: + return false + } + + C.MagickSetImageOrientation(cv.wand, (C.OrientationType)(TOP_LEFT_ORIENTATION)) + cv.SetMetadata("exif:Orientation", (string)(TOP_LEFT_ORIENTATION)) + + return true +} + +// Returns all metadata keys from the currently loaded image. +func (cv Canvas) Metadata() map[string]string { + var n C.size_t + var i C.size_t + + var value *C.char + var key *C.char + + data := make(map[string]string) + + properties := C.MagickGetImageProperties(cv.wand, C.CString("*"), &n) + + for i = 0; i < n; i++ { + key = C.MagickGetPropertyName(properties, i) + value = C.MagickGetImageProperty(cv.wand, key) + + data[strings.Trim(C.GoString(key), " ")] = strings.Trim(C.GoString(value), " ") + + C.MagickRelinquishMemory(unsafe.Pointer(value)) + C.MagickRelinquishMemory(unsafe.Pointer(key)) + } + + return data +} + +// Associates a metadata key with its value. +func (cv Canvas) SetMetadata(key string, value string) { + C.MagickSetImageProperty(cv.wand, C.CString(key), C.CString(value)) +} + +// Creates a horizontal mirror image by reflecting the pixels around the central y-axis. +func (cv Canvas) Flop() bool { + status := C.MagickFlopImage(cv.wand) + if status == C.MagickFalse { + return false + } + return true +} + +// Creates a vertical mirror image by reflecting the pixels around the central x-axis. +func (cv Canvas) Flip() bool { + status := C.MagickFlipImage(cv.wand) + if status == C.MagickFalse { + return false + } + return true +} + +// Creates a centered thumbnail of the canvas. +func (cv Canvas) Thumbnail(width uint, height uint) bool { + + var ratio float64 + + // Normalizing image. + + ratio = math.Min(float64(cv.Width())/float64(width), float64(cv.Height())/float64(height)) + + if ratio < 1.0 { + // Origin image is smaller than the thumbnail image. + max := uint(math.Max(float64(width), float64(height))) + + // Empty replacement buffer with transparent background. + replacement := New() + + replacement.SetBackgroundColor("none") + + replacement.Blank(max, max) + + // Putting original image in the center of the replacement canvas. + replacement.AppendCanvas(cv, int(int(width-cv.Width())/2), int(int(height-cv.Height())/2)) + + // Replacing wand + C.DestroyMagickWand(cv.wand) + + cv.wand = C.CloneMagickWand(replacement.wand) + + } else { + // Is bigger, just resizing. + cv.Resize(uint(float64(cv.Width())/ratio), uint(float64(cv.Height())/ratio)) + } + + // Now we have an image that we can use to crop the thumbnail from. + cv.Crop(int(int(cv.Width()-width)/2), int(int(cv.Height()-height)/2), width, height) + + return true + +} + +// Puts a canvas on top of the current one. +func (cv Canvas) AppendCanvas(source Canvas, x int, y int) bool { + status := C.MagickCompositeImage(cv.wand, source.wand, C.OverCompositeOp, C.ssize_t(x), C.ssize_t(y)) + if status == C.MagickFalse { + return false + } + return true +} + +// Rotates the whole canvas. +func (cv Canvas) RotateCanvas(rad float64) { + C.MagickRotateImage(cv.wand, cv.bg, C.double(RAD_TO_DEG*rad)) +} + +// Returns canvas' width. +func (cv Canvas) Width() uint { + return uint(C.MagickGetImageWidth(cv.wand)) +} + +// Returns canvas' height. +func (cv Canvas) Height() uint { + return uint(C.MagickGetImageHeight(cv.wand)) +} + +// Writes canvas to a file, returns true on success. +func (cv Canvas) Write(filename string) bool { + cv.Update() + status := C.MagickWriteImage(cv.wand, C.CString(filename)) + if status == C.MagickFalse { + return false + } + return true +} + +// Changes the size of the canvas, returns true on success. +func (cv Canvas) Resize(width uint, height uint) bool { + status := C.MagickResizeImage(cv.wand, C.size_t(width), C.size_t(height), C.GaussianFilter, C.double(1.0)) + if status == C.MagickFalse { + return false + } + return true +} + +// Adaptively changes the size of the canvas, returns true on success. +func (cv Canvas) AdaptiveResize(width uint, height uint) bool { + status := C.MagickAdaptiveResizeImage(cv.wand, C.size_t(width), C.size_t(height)) + if status == C.MagickFalse { + return false + } + return true +} + +// Changes the compression quality of the canvas. Ranges from 1 (lowest) to 100 (highest). +func (cv Canvas) SetQuality(quality uint) bool { + status := C.MagickSetImageCompressionQuality(cv.wand, C.size_t(quality)) + if status == C.MagickFalse { + return false + } + return true +} + +// Returns the compression quality of the canvas. Ranges from 1 (lowest) to 100 (highest). +func (cv Canvas) Quality() uint { + return uint(C.MagickGetImageCompressionQuality(cv.wand)) +} + +/* +// Sets canvas's foreground color. +func (cv Canvas) SetColor(color string) (bool) { + status := C.PixelSetColor(cv.fg, C.CString(color)) + if status == C.MagickFalse { + return false + } + return true +} +*/ + +// Sets canvas' background color. +func (cv Canvas) SetBackgroundColor(color string) bool { + C.PixelSetColor(cv.bg, C.CString(color)) + status := C.MagickSetImageBackgroundColor(cv.wand, cv.bg) + if status == C.MagickFalse { + return false + } + return true +} + +// Returns canvas' background color. +func (cv Canvas) BackgroundColor() string { + return getPixelHexColor(cv.bg) +} + +// Sets antialiasing setting for the current drawing stroke. +func (cv Canvas) SetStrokeAntialias(value bool) { + C.DrawSetStrokeAntialias(cv.drawing, magickBoolean(value)) +} + +// Returns antialiasing setting for the current drawing stroke. +func (cv Canvas) StrokeAntialias() bool { + value := C.DrawGetStrokeAntialias(cv.drawing) + if value == C.MagickTrue { + return true + } + return false +} + +// Sets the width of the stroke on the current drawing surface. +func (cv Canvas) SetStrokeWidth(value float64) { + C.DrawSetStrokeWidth(cv.drawing, C.double(value)) +} + +// Returns the width of the stroke on the current drawing surface. +func (cv Canvas) StrokeWidth() float64 { + return float64(C.DrawGetStrokeWidth(cv.drawing)) +} + +// Sets the opacity of the stroke on the current drawing surface. +func (cv Canvas) SetStrokeOpacity(value float64) { + C.DrawSetStrokeOpacity(cv.drawing, C.double(value)) +} + +// Returns the opacity of the stroke on the current drawing surface. +func (cv Canvas) StrokeOpacity() float64 { + return float64(C.DrawGetStrokeOpacity(cv.drawing)) +} + +// Sets the type of the line cap on the current drawing surface. +func (cv Canvas) SetStrokeLineCap(value uint) { + C.DrawSetStrokeLineCap(cv.drawing, C.LineCap(value)) +} + +// Returns the type of the line cap on the current drawing surface. +func (cv Canvas) StrokeLineCap() uint { + return uint(C.DrawGetStrokeLineCap(cv.drawing)) +} + +// Sets the type of the line join on the current drawing surface. +func (cv Canvas) SetStrokeLineJoin(value uint) { + C.DrawSetStrokeLineJoin(cv.drawing, C.LineJoin(value)) +} + +// Returns the type of the line join on the current drawing surface. +func (cv Canvas) StrokeLineJoin() uint { + return uint(C.DrawGetStrokeLineJoin(cv.drawing)) +} + +/* +func (cv Canvas) SetFillRule(value int) { + C.DrawSetFillRule(cv.drawing, C.FillRule(value)) +} +*/ + +// Sets the fill color for enclosed areas on the current drawing surface. +func (cv Canvas) SetFillColor(color string) { + C.PixelSetColor(cv.fill, C.CString(color)) + C.DrawSetFillColor(cv.drawing, cv.fill) +} + +// Returns the fill color for enclosed areas on the current drawing surface. +func (cv Canvas) FillColor() string { + return getPixelHexColor(cv.fill) +} + +// Sets the stroke color on the current drawing surface. +func (cv Canvas) SetStrokeColor(color string) { + C.PixelSetColor(cv.stroke, C.CString(color)) + C.DrawSetStrokeColor(cv.drawing, cv.stroke) +} + +// Returns the stroke color on the current drawing surface. +func (cv Canvas) StrokeColor() string { + return getPixelHexColor(cv.stroke) +} + +// Draws a circle over the current drawing surface. +func (cv Canvas) Circle(radius float64) { + C.DrawCircle(cv.drawing, C.double(0), C.double(0), C.double(radius), C.double(0)) +} + +// Draws a rectangle over the current drawing surface. +func (cv Canvas) Rectangle(x float64, y float64) { + C.DrawRectangle(cv.drawing, C.double(0), C.double(0), C.double(x), C.double(y)) +} + +// Moves the current coordinate system origin to the specified coordinate. +func (cv Canvas) Translate(x float64, y float64) { + C.DrawTranslate(cv.drawing, C.double(x), C.double(y)) +} + +// Applies a scaling factor to the units of the current coordinate system. +func (cv Canvas) Scale(x float64, y float64) { + C.DrawScale(cv.drawing, C.double(x), C.double(y)) +} + +// Draws a line starting on the current coordinate system origin and ending on the specified coordinates. +func (cv Canvas) Line(x float64, y float64) { + C.DrawLine(cv.drawing, C.double(0), C.double(0), C.double(x), C.double(y)) +} + +/* +func (cv Canvas) Skew(x float64, y float64) { + C.DrawSkewX(cv.drawing, C.double(x)) + C.DrawSkewY(cv.drawing, C.double(y)) +} +*/ + +// Applies a rotation of a given angle (in radians) on the current coordinate system. +func (cv Canvas) Rotate(rad float64) { + deg := RAD_TO_DEG * rad + C.DrawRotate(cv.drawing, C.double(deg)) +} + +// Draws an ellipse centered at the current coordinate system's origin. +func (cv Canvas) Ellipse(a float64, b float64) { + C.DrawEllipse(cv.drawing, C.double(0), C.double(0), C.double(a), C.double(b), 0, 360) +} + +// Clones the current drawing surface and stores it in a stack. +func (cv Canvas) PushDrawing() bool { + status := C.PushDrawingWand(cv.drawing) + if status == C.MagickFalse { + return false + } + return true +} + +// Destroys the current drawing surface and returns the latest surface that was pushed to the stack. +func (cv Canvas) PopDrawing() bool { + status := C.PopDrawingWand(cv.drawing) + if status == C.MagickFalse { + return false + } + return true +} + +// Copies a drawing surface to the canvas. +func (cv Canvas) Update() { + C.MagickDrawImage(cv.wand, cv.drawing) +} + +// Destroys canvas. +func (cv Canvas) Destroy() { + if cv.wand != nil { + C.DestroyMagickWand(cv.wand) + } + C.MagickWandTerminus() +} + +// Creates an empty canvas of the given dimensions. +func (cv Canvas) Blank(width uint, height uint) bool { + status := C.MagickNewImage(cv.wand, C.size_t(width), C.size_t(height), cv.bg) + if status == C.MagickFalse { + return false + } + return true +} + +// Convolves the canvas with a Gaussian function given its standard deviation. +func (cv Canvas) Blur(sigma float64) bool { + status := C.MagickBlurImage(cv.wand, C.double(0), C.double(sigma)) + if status == C.MagickFalse { + return false + } + return true +} + +// Adaptively blurs the image by blurring less intensely near the edges and more intensely far from edges. +func (cv Canvas) AdaptiveBlur(sigma float64) bool { + status := C.MagickAdaptiveBlurImage(cv.wand, C.double(0), C.double(sigma)) + if status == C.MagickFalse { + return false + } + return true +} + +// Adds random noise to the canvas. +func (cv Canvas) AddNoise() bool { + status := C.MagickAddNoiseImage(cv.wand, C.GaussianNoise) + if status == C.MagickFalse { + return false + } + return true +} + +// Removes a region of a canvas and collapses the canvas to occupy the removed portion. +func (cv Canvas) Chop(x int, y int, width uint, height uint) bool { + status := C.MagickChopImage(cv.wand, C.size_t(width), C.size_t(height), C.ssize_t(x), C.ssize_t(y)) + if status == C.MagickFalse { + return false + } + return true +} + +// Extracts a region from the canvas. +func (cv Canvas) Crop(x int, y int, width uint, height uint) bool { + status := C.MagickCropImage(cv.wand, C.size_t(width), C.size_t(height), C.ssize_t(x), C.ssize_t(y)) + if status == C.MagickFalse { + return false + } + return true +} + +// Adjusts the canvas's brightness given a factor (-1.0 thru 1.0) +func (cv Canvas) SetBrightness(factor float64) bool { + + factor = math.Max(-1, factor) + factor = math.Min(1, factor) + + status := C.MagickModulateImage(cv.wand, C.double(100+factor*100.0), C.double(100), C.double(100)) + + if status == C.MagickFalse { + return false + } + + return true +} + +// Adjusts the canvas's saturation given a factor (-1.0 thru 1.0) +func (cv Canvas) SetSaturation(factor float64) bool { + + factor = math.Max(-1, factor) + factor = math.Min(1, factor) + + status := C.MagickModulateImage(cv.wand, C.double(100), C.double(100+factor*100.0), C.double(100)) + + if status == C.MagickFalse { + return false + } + + return true +} + +// Adjusts the canvas's hue given a factor (-1.0 thru 1.0) +func (cv Canvas) SetHue(factor float64) bool { + + factor = math.Max(-1, factor) + factor = math.Min(1, factor) + + status := C.MagickModulateImage(cv.wand, C.double(100), C.double(100), C.double(100+factor*100.0)) + + if status == C.MagickFalse { + return false + } + + return true +} + +// Returns a new canvas object. +func New() *Canvas { + cv := &Canvas{} + + cv.Init() + + cv.wand = C.NewMagickWand() + + cv.fg = C.NewPixelWand() + cv.bg = C.NewPixelWand() + + cv.fill = C.NewPixelWand() + cv.stroke = C.NewPixelWand() + + cv.drawing = C.NewDrawingWand() + + //cv.SetColor("#ffffff") + cv.SetBackgroundColor("none") + + cv.SetStrokeColor("#ffffff") + cv.SetStrokeAntialias(true) + cv.SetStrokeWidth(1.0) + cv.SetStrokeOpacity(1.0) + cv.SetStrokeLineCap(STROKE_ROUND_CAP) + cv.SetStrokeLineJoin(STROKE_ROUND_JOIN) + + //cv.SetFillRule(FILL_EVEN_ODD_RULE) + cv.SetFillColor("#888888") + + return cv +} diff --git a/canvas/canvas_test.go b/canvas/canvas_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2b720f4b4d69fa072b455536dff0f9b0fe7ddee0 --- /dev/null +++ b/canvas/canvas_test.go @@ -0,0 +1,332 @@ +package canvas + +import "testing" + +import "math" + +/* + Example image is form Yuko Honda + http://www.flickr.com/photos/yukop/6779040884/ +*/ + +func TestOpenWrite(t *testing.T) { + canvas := New() + + opened := canvas.Open("examples/input/example.png") + + if opened { + canvas.AutoOrientate() + + canvas.SetQuality(90) + + canvas.Write("examples/output/example.jpg") + } + + canvas.Destroy() +} + +func TestThumbnail(t *testing.T) { + canvas := New() + + opened := canvas.Open("examples/input/example.png") + + if opened { + canvas.AutoOrientate() + + canvas.Thumbnail(100, 100) + + canvas.Write("examples/output/example-thumbnail.png") + } + + canvas.Destroy() +} + +func TestResize(t *testing.T) { + canvas := New() + + opened := canvas.Open("examples/input/example.png") + + if opened { + canvas.Resize(100, 100) + canvas.Write("examples/output/example-100x100.png") + } + + canvas.Destroy() +} + +func TestBlank(t *testing.T) { + canvas := New() + + canvas.SetBackgroundColor("#00ff00") + + success := canvas.Blank(400, 400) + + if success { + canvas.Write("examples/output/example-blank.png") + } + + canvas.Destroy() +} + +func TestSettersAndGetters(t *testing.T) { + + canvas := New() + + success := canvas.Blank(400, 400) + if success != true { + t.Errorf("Could not create blank image.") + } + + const backgroundColor = "#112233" + + canvas.SetBackgroundColor(backgroundColor) + + if gotBackgroundColor := canvas.BackgroundColor(); gotBackgroundColor != backgroundColor { + t.Errorf("Got %s, expecting %s", gotBackgroundColor, backgroundColor) + } + + const strokeAntialias = true + + canvas.SetStrokeAntialias(strokeAntialias) + + if gotStrokeAntialias := canvas.StrokeAntialias(); gotStrokeAntialias != strokeAntialias { + t.Errorf("Got %t, expecting %t.", gotStrokeAntialias, strokeAntialias) + } + + const strokeWidth = 2.0 + + canvas.SetStrokeWidth(strokeWidth) + + if gotStrokeWidth := canvas.StrokeWidth(); gotStrokeWidth != strokeWidth { + t.Errorf("Got %f, expecting %f.", gotStrokeWidth, strokeWidth) + } + + const strokeOpacity = 1.0 + + canvas.SetStrokeOpacity(strokeOpacity) + + if gotStrokeOpacity := canvas.StrokeOpacity(); gotStrokeOpacity != strokeOpacity { + t.Errorf("Got %f, expecting %f.", gotStrokeOpacity, strokeOpacity) + } + + strokeLineCap := STROKE_SQUARE_CAP + + canvas.SetStrokeLineCap(strokeLineCap) + + if gotStrokeLineCap := canvas.StrokeLineCap(); gotStrokeLineCap != strokeLineCap { + t.Errorf("Got %d, expecting %d.", gotStrokeLineCap, strokeLineCap) + } + + strokeLineJoin := STROKE_ROUND_JOIN + + canvas.SetStrokeLineJoin(strokeLineJoin) + + if gotStrokeLineJoin := canvas.StrokeLineJoin(); gotStrokeLineJoin != strokeLineJoin { + t.Errorf("Got %d, expecting %d.", gotStrokeLineJoin, strokeLineJoin) + } + + const fillColor = "#112233" + + canvas.SetFillColor(fillColor) + + if gotFillColor := canvas.FillColor(); gotFillColor != fillColor { + t.Errorf("Got %s, expecting %s", gotFillColor, fillColor) + } + + const strokeColor = "#112233" + + canvas.SetStrokeColor(strokeColor) + + if gotStrokeColor := canvas.StrokeColor(); gotStrokeColor != strokeColor { + t.Errorf("Got %s, expecting %s", gotStrokeColor, strokeColor) + } + + const quality = 76 + + canvas.SetQuality(quality) + + if gotQuality := canvas.Quality(); gotQuality != quality { + t.Errorf("Got %d, expecting %d", gotQuality, quality) + } + + canvas.Destroy() +} + +func TestDrawLine(t *testing.T) { + + canvas := New() + + canvas.SetBackgroundColor("#000000") + + success := canvas.Blank(400, 400) + + if success { + + canvas.Translate(200, 200) + canvas.SetStrokeWidth(10) + canvas.SetStrokeColor("#ffffff") + canvas.Line(100, 100) + + canvas.Write("examples/output/example-line.png") + } + + canvas.Destroy() +} + +func TestDrawCircle(t *testing.T) { + canvas := New() + + canvas.SetBackgroundColor("#000000") + + success := canvas.Blank(400, 400) + + if success { + + canvas.SetFillColor("#ff0000") + + canvas.PushDrawing() + canvas.Translate(200, 200) + canvas.SetStrokeWidth(5) + canvas.SetStrokeColor("#ffffff") + canvas.Circle(100) + canvas.PopDrawing() + + canvas.PushDrawing() + canvas.Translate(100, 100) + canvas.SetStrokeWidth(3) + canvas.SetStrokeColor("#ffffff") + canvas.Circle(20) + canvas.PopDrawing() + + canvas.Write("examples/output/example-circle.png") + } + + canvas.Destroy() +} + +func TestDrawRectangle(t *testing.T) { + canvas := New() + + canvas.SetBackgroundColor("#000000") + + success := canvas.Blank(400, 400) + + if success { + + canvas.SetFillColor("#ff0000") + + canvas.Translate(200-50, 200+75) + canvas.SetStrokeWidth(5) + canvas.SetStrokeColor("#ffffff") + canvas.Rectangle(100, -150) + + canvas.Write("examples/output/example-rectangle.png") + } + + canvas.Destroy() +} + +func TestDrawEllipse(t *testing.T) { + canvas := New() + + success := canvas.Blank(400, 400) + + if success { + + canvas.SetFillColor("#ff0000") + + canvas.PushDrawing() + canvas.Translate(200, 200) + canvas.Rotate(math.Pi / 3) + canvas.Ellipse(50, 180) + canvas.PopDrawing() + + canvas.SetFillColor("#ff00ff") + + canvas.PushDrawing() + canvas.Translate(200, 200) + canvas.Rotate(-math.Pi / 3) + canvas.Ellipse(25, 90) + canvas.PopDrawing() + + canvas.Write("examples/output/example-ellipse.png") + } + + canvas.Destroy() +} + +func TestBlur(t *testing.T) { + canvas := New() + defer canvas.Destroy() + + opened := canvas.Open("examples/input/example.png") + + if opened { + canvas.Blur(3) + canvas.Write("examples/output/example-blur.png") + } +} + +func TestModulate(t *testing.T) { + canvas := New() + defer canvas.Destroy() + + opened := canvas.Open("examples/input/example.png") + + if opened { + canvas.SetBrightness(-0.5) + canvas.SetHue(0.2) + canvas.SetSaturation(0.9) + canvas.Write("examples/output/example-modulate.png") + } +} + +func TestAdaptive(t *testing.T) { + + canvas := New() + defer canvas.Destroy() + + opened := canvas.Open("examples/input/example.png") + + if opened { + canvas.AdaptiveBlur(1.2) + canvas.AdaptiveResize(100, 100) + canvas.Write("examples/output/example-adaptive.png") + } +} + +func TestNoise(t *testing.T) { + canvas := New() + defer canvas.Destroy() + + opened := canvas.Open("examples/input/example.png") + + if opened { + canvas.AddNoise() + canvas.Write("examples/output/example-noise.png") + } +} + +func TestChop(t *testing.T) { + canvas := New() + defer canvas.Destroy() + + opened := canvas.Open("examples/input/example.png") + + if opened { + canvas.Chop(0, 0, 100, 50) + canvas.Write("examples/output/example-chop.png") + } +} + +func TestCrop(t *testing.T) { + canvas := New() + defer canvas.Destroy() + + opened := canvas.Open("examples/input/example.png") + + if opened { + canvas.Crop(100, 200, 200, 100) + canvas.Write("examples/output/example-crop.png") + } +} diff --git a/canvas/examples/input/example.png b/canvas/examples/input/example.png new file mode 100644 index 0000000000000000000000000000000000000000..a159f593fd0d926fc4b24dddb46908bbeff7e605 Binary files /dev/null and b/canvas/examples/input/example.png differ diff --git a/canvas/examples/output/empty b/canvas/examples/output/empty new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391