diff --git a/examples/convert_command/main.go b/examples/convert_command/main.go new file mode 100644 index 0000000..63c714d --- /dev/null +++ b/examples/convert_command/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + + "gopkg.in/gographics/imagick.v3/imagick" +) + +func main() { + imagick.Initialize() + defer imagick.Terminate() + + ret, err := imagick.ConvertImageCommand([]string{ + "convert", "logo:", "-resize", "100x100", "/tmp/out.png", + }) + if err != nil { + panic(err) + } + + fmt.Printf("Metadata:\n%s\n", ret.Meta) +} diff --git a/examples/docker/go.mod b/examples/docker/go.mod index 1c4a3bd..b142ea7 100644 --- a/examples/docker/go.mod +++ b/examples/docker/go.mod @@ -2,3 +2,4 @@ module resizer require gopkg.in/gographics/imagick.v2 v2.5.0 +go 1.13 diff --git a/examples/docker/go.sum b/examples/docker/go.sum new file mode 100644 index 0000000..be8af5f --- /dev/null +++ b/examples/docker/go.sum @@ -0,0 +1 @@ +gopkg.in/gographics/imagick.v2 v2.5.0/go.mod h1:of4TbGX8yMcpgWkWFjha7FsOFr+NjOJ5O1qtKU27Yj0= diff --git a/imagick/exception_info.go b/imagick/exception_info.go new file mode 100644 index 0000000..cf20ced --- /dev/null +++ b/imagick/exception_info.go @@ -0,0 +1,78 @@ +// Copyright 2013 Herbert G. Fischer. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package imagick + +/* +#include +*/ +import "C" + +import "fmt" + +// ExceptionInfo is an error type returned by certain +// New* API calls. +type ExceptionInfo struct { + kind ExceptionType + errno int + reason string + description string +} + +// Create a new ExceptionInfo wrapper around a C ExceptionInfo ptr +func newExceptionInfo(errInfo *C.ExceptionInfo) *ExceptionInfo { + if errInfo == nil { + return nil + } + + return &ExceptionInfo{ + kind: ExceptionType(errInfo.severity), + errno: int(errInfo.error_number), + reason: C.GoString(errInfo.reason), + description: C.GoString(errInfo.description), + } +} + +// Check if a given C ExceptionInfo ptr is an error. +// Returns a valid ExceptionInfo if there was an error, +// otherwise returns nil +func checkExceptionInfo(errInfo *C.ExceptionInfo) *ExceptionInfo { + if errInfo != nil && errInfo.error_number != 0 { + return newExceptionInfo(errInfo) + } + + return nil +} + +func (e *ExceptionInfo) Error() string { + if e == nil { + return "" + } + return fmt.Sprintf("%s (%d): %s %s", + e.kind.String(), e.Errno(), e.Reason(), e.Description()) +} + +// Errno returns the ExceptionInfo error number (non-zero if error) +func (e *ExceptionInfo) Errno() int { + if e == nil { + return 0 + } + return e.errno +} + +// Reason returns the string reason for the Exception +func (e *ExceptionInfo) Reason() string { + if e == nil { + return "" + } + return e.reason +} + +// Description returns the string description for the Exception +func (e *ExceptionInfo) Description() string { + if e == nil { + return "" + } + return e.description +} diff --git a/imagick/image.go b/imagick/image.go index e4f28aa..92bdb66 100644 --- a/imagick/image.go +++ b/imagick/image.go @@ -6,9 +6,13 @@ package imagick /* #include +#include */ import "C" -import "runtime" +import ( + "runtime" + "unsafe" +) type Image struct { img *C.Image @@ -20,3 +24,61 @@ func NewMagickImage(info *ImageInfo, width, height uint, background *MagickPixel runtime.KeepAlive(background) return ret } + +// ImageCommandResult is returned by a call to +// ConvertImageCommand. It contains the ImageInfo +// and Metadata string generated by the convert command. +type ImageCommandResult struct { + Info *ImageInfo + Meta string +} + +/* +ConvertImageCommand reads one or more images, applies one or more image +processing operations, and writes out the image in the same or differing +format. +The first item in the args list is expected to be the program name, ie "convert". +*/ +func ConvertImageCommand(args []string) (*ImageCommandResult, error) { + size := len(args) + + cmdArr := make([]*C.char, size) + for i, s := range args { + cmdArr[i] = C.CString(s) + } + + empty := C.CString("") + metaStr := C.AcquireString(empty) + C.free(unsafe.Pointer(empty)) + + defer func() { + for i := range cmdArr { + C.free(unsafe.Pointer(cmdArr[i])) + } + + C.DestroyString(metaStr) + }() + + imageInfo := newImageInfo() + + var exc *C.ExceptionInfo = C.AcquireExceptionInfo() + defer C.DestroyExceptionInfo(exc) + + ok := C.ConvertImageCommand( + imageInfo.info, + C.int(size), // argc + &cmdArr[0], // argv + &metaStr, // metadata + exc, // exception + ) + if C.int(ok) == 0 { + imageInfo.Destroy() + return nil, newExceptionInfo(exc) + } + + ret := &ImageCommandResult{ + Info: imageInfo, + Meta: C.GoString(metaStr), + } + return ret, nil +} diff --git a/imagick/image_info.go b/imagick/image_info.go index aff7cbb..4f7be52 100644 --- a/imagick/image_info.go +++ b/imagick/image_info.go @@ -8,7 +8,25 @@ package imagick #include */ import "C" +import "runtime" type ImageInfo struct { info *C.ImageInfo } + +func newImageInfo() *ImageInfo { + ptr := C.AcquireImageInfo() + C.GetImageInfo(ptr) + imageInfo := &ImageInfo{ptr} + runtime.SetFinalizer(imageInfo, Destroy) + return imageInfo +} + +// Destroy the ImageInfo immediately. +// This will also be called automatically during garbage collection. +func (ii *ImageInfo) Destroy() { + if ii.info != nil { + C.DestroyImageInfo(ii.info) + ii.info = nil + } +} diff --git a/imagick/image_test.go b/imagick/image_test.go new file mode 100644 index 0000000..aa0190b --- /dev/null +++ b/imagick/image_test.go @@ -0,0 +1,62 @@ +// Copyright 2013 Herbert G. Fischer. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package imagick + +import ( + "fmt" + "io/ioutil" + "os" + "testing" +) + +func init() { + Initialize() +} + +// TODO(justinfx): Need to expand test to try and +// produce and catch an error from NewPixelWand +func TestNewMagickImage(t *testing.T) { + info := newImageInfo() + defer info.Destroy() + + pp, err := QueryMagickColor("blue") + if err != nil { + t.Fatal(err.Error()) + } + + img := NewMagickImage(info, 100, 100, pp) + if img == nil { + t.Fatal("nil MagickImage") + } +} + +func ExampleConvertImageCommand() { + ret, err := ConvertImageCommand([]string{ + "convert", "logo:", "-resize", "100x100", "/tmp/out.png", + }) + if err != nil { + panic(err) + } + fmt.Println("Meta:", ret.Meta) +} + +func TestConvertImageCommand(t *testing.T) { + tmp, err := ioutil.TempFile("", "imagick_test") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmp.Name()) + + ret, err := ConvertImageCommand([]string{ + "convert", "logo:", "-resize", "100x100", tmp.Name(), + }) + if err != nil { + t.Fatalf("command failed: %v", err) + } + + if ret.Meta == "" { + t.Fatal("got empty metadata string from command result") + } +} diff --git a/imagick/magick_pixel_packet.go b/imagick/magick_pixel_packet.go index 0db0c72..774d799 100644 --- a/imagick/magick_pixel_packet.go +++ b/imagick/magick_pixel_packet.go @@ -8,11 +8,77 @@ package imagick #include */ import "C" +import "unsafe" +// MagickPixelPacket is constructed using NewMagickPixelPacket type MagickPixelPacket struct { mpp *C.MagickPixelPacket } +func (p *MagickPixelPacket) Red() float64 { + return float64(p.mpp.red) +} + +func (p *MagickPixelPacket) SetRed(value float64) { + p.mpp.red = C.MagickRealType(value) +} + +func (p *MagickPixelPacket) Green() float64 { + return float64(p.mpp.green) +} + +func (p *MagickPixelPacket) SetGreen(value float64) { + p.mpp.green = C.MagickRealType(value) +} + +func (p *MagickPixelPacket) Blue() float64 { + return float64(p.mpp.blue) +} + +func (p *MagickPixelPacket) SetBlue(value float64) { + p.mpp.blue = C.MagickRealType(value) +} + +func (p *MagickPixelPacket) Opacity() float64 { + return float64(p.mpp.opacity) +} + +func (p *MagickPixelPacket) SetOpacity(value float64) { + p.mpp.opacity = C.MagickRealType(value) +} + +func (p *MagickPixelPacket) Index() float64 { + return float64(p.mpp.index) +} + +func (p *MagickPixelPacket) SetIndex(value float64) { + p.mpp.index = C.MagickRealType(value) +} + +func NewMagickPixelPacket() *MagickPixelPacket { + var mpp C.MagickPixelPacket + return &MagickPixelPacket{mpp: &mpp} +} + func newMagickPixelPacketFromCAPI(mpp *C.MagickPixelPacket) *MagickPixelPacket { return &MagickPixelPacket{mpp} } + +// QueryMagickColor returns the red, green, blue, and opacity +// intensities for a given color name. +func QueryMagickColor(colorName string) (*MagickPixelPacket, error) { + cstr := C.CString(colorName) + defer C.free(unsafe.Pointer(cstr)) + + var mpp C.MagickPixelPacket + + var exc *C.ExceptionInfo = C.AcquireExceptionInfo() + defer C.DestroyExceptionInfo(exc) + + ok := C.QueryMagickColor(cstr, &mpp, exc) + if C.int(ok) == 0 { + return nil, newExceptionInfo(exc) + } + + return newMagickPixelPacketFromCAPI(&mpp), nil +}