diff --git a/debug.go b/debug.go index c781668..9f6fcea 100644 --- a/debug.go +++ b/debug.go @@ -1,5 +1,62 @@ package bimg -import . "github.com/tj/go-debug" +import ( + "github.com/dustin/go-humanize" + . "github.com/tj/go-debug" + "runtime" + "strconv" + "time" +) var debug = Debug("bimg") + +// Print Go memory and garbage collector stats. Useful for debugging +func PrintMemoryStats() { + log := Debug("memory") + mem := memoryStats() + + log("\u001b[33m---- Memory Dump Stats ----\u001b[39m") + log("Allocated: %s", humanize.Bytes(mem.Alloc)) + log("Total Allocated: %s", humanize.Bytes(mem.TotalAlloc)) + log("Memory Allocations: %d", mem.Mallocs) + log("Memory Frees: %d", mem.Frees) + log("Heap Allocated: %s", humanize.Bytes(mem.HeapAlloc)) + log("Heap System: %s", humanize.Bytes(mem.HeapSys)) + log("Heap In Use: %s", humanize.Bytes(mem.HeapInuse)) + log("Heap Idle: %s", humanize.Bytes(mem.HeapIdle)) + log("Heap OS Related: %s", humanize.Bytes(mem.HeapReleased)) + log("Heap Objects: %s", humanize.Bytes(mem.HeapObjects)) + log("Stack In Use: %s", humanize.Bytes(mem.StackInuse)) + log("Stack System: %s", humanize.Bytes(mem.StackSys)) + log("Stack Span In Use: %s", humanize.Bytes(mem.MSpanInuse)) + log("Stack Cache In Use: %s", humanize.Bytes(mem.MCacheInuse)) + log("Next GC cycle: %s", humanizeNano(mem.NextGC)) + log("Last GC cycle: %s", humanize.Time(time.Unix(0, int64(mem.LastGC)))) + log("\u001b[33m---- End Memory Dump ----\u001b[39m") +} + +func memoryStats() runtime.MemStats { + var mem runtime.MemStats + runtime.ReadMemStats(&mem) + return mem +} + +func humanizeNano(n uint64) string { + var suffix string + + switch { + case n > 1e9: + n /= 1e9 + suffix = "s" + case n > 1e6: + n /= 1e6 + suffix = "ms" + case n > 1e3: + n /= 1e3 + suffix = "us" + default: + suffix = "ns" + } + + return strconv.Itoa(int(n)) + suffix +} diff --git a/image.go b/image.go index 2b00d63..bf6d03a 100644 --- a/image.go +++ b/image.go @@ -75,6 +75,23 @@ func (i *Image) Thumbnail(pixels int) ([]byte, error) { return i.Process(options) } +// Insert an image. Alias to Watermark() +func (i *Image) Insert(image []byte, left, top int) ([]byte, error) { + return i.Watermark(image, left, top) +} + +// Insert an image to the existent one as watermark +func (i *Image) Watermark(image []byte, left, top int) ([]byte, error) { + options := Options{ + Insert: Insert{ + Buffer: image, + Top: top, + Left: left, + }, + } + return i.Process(options) +} + // Rotate the image by given angle degrees (0, 90, 180 or 270) func (i *Image) Rotate(a Angle) ([]byte, error) { options := Options{Rotate: a} diff --git a/image_test.go b/image_test.go index ffbeff3..f5310d1 100644 --- a/image_test.go +++ b/image_test.go @@ -104,6 +104,31 @@ func TestImageThumbnail(t *testing.T) { Write("fixtures/test_thumbnail_out.jpg", buf) } +func TestImageWatermark(t *testing.T) { + image := initImage("test.jpg") + _, err := image.Crop(800, 600, NORTH) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + insert, _ := Read("fixtures/watermark.png") + buf, err := image.Watermark(insert, 10, 10) + if err != nil { + t.Error(err) + } + + err = assertSize(buf, 800, 600) + if err != nil { + //t.Error(err) + } + + if DetermineImageType(buf) != PNG { + //t.Fatal("Image is not jpeg") + } + + Write("fixtures/test_watermark_out.jpg", buf) +} + func TestImageFlip(t *testing.T) { buf, err := initImage("test.jpg").Flip() if err != nil { diff --git a/options.go b/options.go index e05c618..e5025ce 100644 --- a/options.go +++ b/options.go @@ -55,6 +55,12 @@ const ( VERTICAL Direction = C.VIPS_DIRECTION_VERTICAL ) +type Insert struct { + Top int + Left int + Buffer []byte +} + type Options struct { Height int Width int @@ -70,7 +76,9 @@ type Options struct { Embed bool Flip bool Flop bool + NoAutoRotate bool Rotate Angle + Insert Insert Gravity Gravity Type ImageType Interpolator Interpolator diff --git a/resize.go b/resize.go index 934d8c9..a69b1fd 100644 --- a/resize.go +++ b/resize.go @@ -8,7 +8,6 @@ import "C" import ( "errors" - "fmt" "math" ) @@ -107,7 +106,7 @@ func Resize(buf []byte, o Options) ([]byte, error) { } } - // Rotate / flip image if necessary based on EXIF metadata + // Rotate / flip image if necessary image, err = rotateImage(image, o) if err != nil { return nil, err @@ -119,6 +118,12 @@ func Resize(buf []byte, o Options) ([]byte, error) { Compression: o.Compression, } + // Insert an image if necessary + image, err = insertImage(image, imageType, o.Insert, saveOptions) + if err != nil { + return nil, err + } + // Finally save as buffer buf, err = vipsSave(image, saveOptions) if err != nil { @@ -146,7 +151,7 @@ func extractImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, break case o.Top > 0 || o.Left > 0: if o.AreaWidth == 0 || o.AreaHeight == 0 { - err = errors.New(fmt.Sprintf("Invalid area to extract %dx%d", o.AreaWidth, o.AreaHeight)) + err = errors.New("Area to extract cannot be 0") } else { image, err = vipsExtract(image, o.Left, o.Top, o.AreaWidth, o.AreaHeight) } @@ -160,12 +165,14 @@ func rotateImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, e var err error var direction Direction = -1 - rotation, flip := calculateRotationAndFlip(image, o.Rotate) - if flip { - o.Flip = flip - } - if rotation > D0 && o.Rotate == 0 { - o.Rotate = rotation + if o.NoAutoRotate == false { + rotation, flip := calculateRotationAndFlip(image, o.Rotate) + if flip { + o.Flip = flip + } + if rotation > D0 && o.Rotate == 0 { + o.Rotate = rotation + } } if o.Rotate > 0 { @@ -185,6 +192,36 @@ func rotateImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, e return image, err } +// WIP +func insertImage(image *C.struct__VipsImage, t ImageType, o Insert, save vipsSaveOptions) (*C.struct__VipsImage, error) { + if len(o.Buffer) == 0 { + return image, nil + } + + insert, imageType, err := vipsRead(o.Buffer) + if err != nil { + return nil, err + } + + if imageType != t { + save.Type = t + debug("Image type insert: %s", save.Type) + buf, err := vipsSave(insert, save) + if err != nil { + return nil, err + } + insert, imageType, err = vipsRead(buf) + debug("New type image: %s", imageType) + if err != nil { + return nil, err + } + } + + debug("Insert images: %#v", insert) + + return vipsInsert(image, insert, o.Left, o.Top) +} + func shrinkImage(image *C.struct__VipsImage, o Options, residual float64, shrink int) (*C.struct__VipsImage, float64, error) { // Use vips_shrink with the integral reduction image, err := vipsShrink(image, shrink) @@ -235,7 +272,6 @@ func imageCalculations(o *Options, inWidth, inHeight int) float64 { factor := 1.0 xfactor := float64(inWidth) / float64(o.Width) yfactor := float64(inHeight) / float64(o.Height) - defer debug("Image calculations: %dx%d", o.Width, o.Height) switch { // Fixed width and height diff --git a/resize_test.go b/resize_test.go index 5f18c31..86e72eb 100644 --- a/resize_test.go +++ b/resize_test.go @@ -175,12 +175,11 @@ func BenchmarkCrop(b *testing.B) { options := Options{ Width: 800, Height: 600, - Crop: true, } runBenchmarkResize("test.jpg", options, b) } -func BenchmarkExtract(b *testing.B) { +func BenchmarkExtractJpeg(b *testing.B) { options := Options{ Top: 100, Left: 50, diff --git a/vips.go b/vips.go index 1006e32..daa89d2 100644 --- a/vips.go +++ b/vips.go @@ -25,6 +25,12 @@ type vipsSaveOptions struct { Type ImageType } +type VipsMemoryInfo struct { + Memory int64 + MemoryHighwater int64 + Allocations int64 +} + func init() { if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 { panic("unsupported old vips version!") @@ -34,7 +40,7 @@ func init() { } // Explicit thread-safe start of libvips. -// You should only call this function if you previously shutdown libvips +// Only call this function if you've previously shutdown libvips func Initialize() { m.Lock() runtime.LockOSThread() @@ -70,6 +76,15 @@ func VipsDebug() { C.im__print_all() } +// Get the allocated memory by vips in bytes +func VipsMemory() VipsMemoryInfo { + return VipsMemoryInfo{ + Memory: int64(C.vips_tracked_get_mem()), + MemoryHighwater: int64(C.vips_tracked_get_mem_highwater()), + Allocations: int64(C.vips_tracked_get_allocs()), + } +} + func vipsRotate(image *C.struct__VipsImage, angle Angle) (*C.struct__VipsImage, error) { var out *C.struct__VipsImage defer C.g_object_unref(C.gpointer(image)) @@ -94,20 +109,33 @@ func vipsFlip(image *C.struct__VipsImage, direction Direction) (*C.struct__VipsI return out, nil } +func vipsInsert(image *C.struct__VipsImage, sub *C.struct__VipsImage, left, top int) (*C.struct__VipsImage, error) { + var out *C.struct__VipsImage + var cache *C.struct__VipsImage + + defer C.g_object_unref(C.gpointer(image)) + defer C.g_object_unref(C.gpointer(sub)) + + err = C.vips_insert_bridge(image, sub, &out, C.int(left), C.int(top)) + if err != 0 { + return nil, catchVipsError() + } + + return out, nil +} + func vipsRead(buf []byte) (*C.struct__VipsImage, ImageType, error) { var image *C.struct__VipsImage imageType := vipsImageType(buf) if imageType == UNKNOWN { - return nil, UNKNOWN, errors.New("Input buffer contains unsupported image format") + return nil, UNKNOWN, errors.New("Unsupported image format") } - // feed it length := C.size_t(len(buf)) imageBuf := unsafe.Pointer(&buf[0]) - imageTypeC := C.int(imageType) - err := C.vips_init_image(imageBuf, length, imageTypeC, &image) + err := C.vips_init_image(imageBuf, length, C.int(imageType), &image) if err != 0 { return nil, UNKNOWN, catchVipsError() } @@ -201,7 +229,6 @@ func vipsEmbed(input *C.struct__VipsImage, left, top, width, height, extend int) func vipsAffine(input *C.struct__VipsImage, residual float64, i Interpolator) (*C.struct__VipsImage, error) { var image *C.struct__VipsImage - istring := C.CString(i.String()) interpolator := C.vips_interpolate_new(istring) @@ -221,6 +248,10 @@ func vipsAffine(input *C.struct__VipsImage, residual float64, i Interpolator) (* func vipsImageType(buf []byte) ImageType { imageType := UNKNOWN + if len(buf) == 0 { + return imageType + } + length := C.size_t(len(buf)) imageBuf := unsafe.Pointer(&buf[0]) bufferType := C.GoString(C.vips_foreign_find_load_buffer(imageBuf, length)) @@ -270,6 +301,5 @@ func catchVipsError() error { s := C.GoString(C.vips_error_buffer()) C.vips_error_clear() C.vips_thread_shutdown() - // clean image memory buffer? return errors.New(s) } diff --git a/vips.h b/vips.h index 9505f81..1813f82 100644 --- a/vips.h +++ b/vips.h @@ -91,6 +91,18 @@ vips_enum_nick_bridge(VipsImage *image) { return vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type); }; +int +vips_insert_bridge(VipsImage *in, VipsImage *sub, VipsImage **out, int left, int top) +{ + return vips_insert(in, sub, out, left, top, NULL); +}; + +int +vips_bandjoin2_bridge(VipsImage *in, VipsImage *sub, VipsImage **out) +{ + return vips_bandjoin2(in, sub, out, NULL); +}; + int vips_embed_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height, int extend) {