mirror of
https://github.com/talgo-cloud/bimg.git
synced 2026-03-15 18:35:54 -07:00
parent
f63020b40f
commit
e603ddd10a
8 changed files with 204 additions and 20 deletions
59
debug.go
59
debug.go
|
|
@ -1,5 +1,62 @@
|
||||||
package bimg
|
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")
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
17
image.go
17
image.go
|
|
@ -75,6 +75,23 @@ func (i *Image) Thumbnail(pixels int) ([]byte, error) {
|
||||||
return i.Process(options)
|
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)
|
// Rotate the image by given angle degrees (0, 90, 180 or 270)
|
||||||
func (i *Image) Rotate(a Angle) ([]byte, error) {
|
func (i *Image) Rotate(a Angle) ([]byte, error) {
|
||||||
options := Options{Rotate: a}
|
options := Options{Rotate: a}
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,31 @@ func TestImageThumbnail(t *testing.T) {
|
||||||
Write("fixtures/test_thumbnail_out.jpg", buf)
|
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) {
|
func TestImageFlip(t *testing.T) {
|
||||||
buf, err := initImage("test.jpg").Flip()
|
buf, err := initImage("test.jpg").Flip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,12 @@ const (
|
||||||
VERTICAL Direction = C.VIPS_DIRECTION_VERTICAL
|
VERTICAL Direction = C.VIPS_DIRECTION_VERTICAL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Insert struct {
|
||||||
|
Top int
|
||||||
|
Left int
|
||||||
|
Buffer []byte
|
||||||
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Height int
|
Height int
|
||||||
Width int
|
Width int
|
||||||
|
|
@ -70,7 +76,9 @@ type Options struct {
|
||||||
Embed bool
|
Embed bool
|
||||||
Flip bool
|
Flip bool
|
||||||
Flop bool
|
Flop bool
|
||||||
|
NoAutoRotate bool
|
||||||
Rotate Angle
|
Rotate Angle
|
||||||
|
Insert Insert
|
||||||
Gravity Gravity
|
Gravity Gravity
|
||||||
Type ImageType
|
Type ImageType
|
||||||
Interpolator Interpolator
|
Interpolator Interpolator
|
||||||
|
|
|
||||||
56
resize.go
56
resize.go
|
|
@ -8,7 +8,6 @@ import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"math"
|
"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)
|
image, err = rotateImage(image, o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -119,6 +118,12 @@ func Resize(buf []byte, o Options) ([]byte, error) {
|
||||||
Compression: o.Compression,
|
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
|
// Finally save as buffer
|
||||||
buf, err = vipsSave(image, saveOptions)
|
buf, err = vipsSave(image, saveOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -146,7 +151,7 @@ func extractImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage,
|
||||||
break
|
break
|
||||||
case o.Top > 0 || o.Left > 0:
|
case o.Top > 0 || o.Left > 0:
|
||||||
if o.AreaWidth == 0 || o.AreaHeight == 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 {
|
} else {
|
||||||
image, err = vipsExtract(image, o.Left, o.Top, o.AreaWidth, o.AreaHeight)
|
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 err error
|
||||||
var direction Direction = -1
|
var direction Direction = -1
|
||||||
|
|
||||||
rotation, flip := calculateRotationAndFlip(image, o.Rotate)
|
if o.NoAutoRotate == false {
|
||||||
if flip {
|
rotation, flip := calculateRotationAndFlip(image, o.Rotate)
|
||||||
o.Flip = flip
|
if flip {
|
||||||
}
|
o.Flip = flip
|
||||||
if rotation > D0 && o.Rotate == 0 {
|
}
|
||||||
o.Rotate = rotation
|
if rotation > D0 && o.Rotate == 0 {
|
||||||
|
o.Rotate = rotation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Rotate > 0 {
|
if o.Rotate > 0 {
|
||||||
|
|
@ -185,6 +192,36 @@ func rotateImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, e
|
||||||
return image, err
|
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) {
|
func shrinkImage(image *C.struct__VipsImage, o Options, residual float64, shrink int) (*C.struct__VipsImage, float64, error) {
|
||||||
// Use vips_shrink with the integral reduction
|
// Use vips_shrink with the integral reduction
|
||||||
image, err := vipsShrink(image, shrink)
|
image, err := vipsShrink(image, shrink)
|
||||||
|
|
@ -235,7 +272,6 @@ func imageCalculations(o *Options, inWidth, inHeight int) float64 {
|
||||||
factor := 1.0
|
factor := 1.0
|
||||||
xfactor := float64(inWidth) / float64(o.Width)
|
xfactor := float64(inWidth) / float64(o.Width)
|
||||||
yfactor := float64(inHeight) / float64(o.Height)
|
yfactor := float64(inHeight) / float64(o.Height)
|
||||||
defer debug("Image calculations: %dx%d", o.Width, o.Height)
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// Fixed width and height
|
// Fixed width and height
|
||||||
|
|
|
||||||
|
|
@ -175,12 +175,11 @@ func BenchmarkCrop(b *testing.B) {
|
||||||
options := Options{
|
options := Options{
|
||||||
Width: 800,
|
Width: 800,
|
||||||
Height: 600,
|
Height: 600,
|
||||||
Crop: true,
|
|
||||||
}
|
}
|
||||||
runBenchmarkResize("test.jpg", options, b)
|
runBenchmarkResize("test.jpg", options, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkExtract(b *testing.B) {
|
func BenchmarkExtractJpeg(b *testing.B) {
|
||||||
options := Options{
|
options := Options{
|
||||||
Top: 100,
|
Top: 100,
|
||||||
Left: 50,
|
Left: 50,
|
||||||
|
|
|
||||||
44
vips.go
44
vips.go
|
|
@ -25,6 +25,12 @@ type vipsSaveOptions struct {
|
||||||
Type ImageType
|
Type ImageType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VipsMemoryInfo struct {
|
||||||
|
Memory int64
|
||||||
|
MemoryHighwater int64
|
||||||
|
Allocations int64
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 {
|
if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 {
|
||||||
panic("unsupported old vips version!")
|
panic("unsupported old vips version!")
|
||||||
|
|
@ -34,7 +40,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicit thread-safe start of libvips.
|
// 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() {
|
func Initialize() {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
|
|
@ -70,6 +76,15 @@ func VipsDebug() {
|
||||||
C.im__print_all()
|
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) {
|
func vipsRotate(image *C.struct__VipsImage, angle Angle) (*C.struct__VipsImage, error) {
|
||||||
var out *C.struct__VipsImage
|
var out *C.struct__VipsImage
|
||||||
defer C.g_object_unref(C.gpointer(image))
|
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
|
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) {
|
func vipsRead(buf []byte) (*C.struct__VipsImage, ImageType, error) {
|
||||||
var image *C.struct__VipsImage
|
var image *C.struct__VipsImage
|
||||||
imageType := vipsImageType(buf)
|
imageType := vipsImageType(buf)
|
||||||
|
|
||||||
if imageType == UNKNOWN {
|
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))
|
length := C.size_t(len(buf))
|
||||||
imageBuf := unsafe.Pointer(&buf[0])
|
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 {
|
if err != 0 {
|
||||||
return nil, UNKNOWN, catchVipsError()
|
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) {
|
func vipsAffine(input *C.struct__VipsImage, residual float64, i Interpolator) (*C.struct__VipsImage, error) {
|
||||||
var image *C.struct__VipsImage
|
var image *C.struct__VipsImage
|
||||||
|
|
||||||
istring := C.CString(i.String())
|
istring := C.CString(i.String())
|
||||||
interpolator := C.vips_interpolate_new(istring)
|
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 {
|
func vipsImageType(buf []byte) ImageType {
|
||||||
imageType := UNKNOWN
|
imageType := UNKNOWN
|
||||||
|
|
||||||
|
if len(buf) == 0 {
|
||||||
|
return imageType
|
||||||
|
}
|
||||||
|
|
||||||
length := C.size_t(len(buf))
|
length := C.size_t(len(buf))
|
||||||
imageBuf := unsafe.Pointer(&buf[0])
|
imageBuf := unsafe.Pointer(&buf[0])
|
||||||
bufferType := C.GoString(C.vips_foreign_find_load_buffer(imageBuf, length))
|
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())
|
s := C.GoString(C.vips_error_buffer())
|
||||||
C.vips_error_clear()
|
C.vips_error_clear()
|
||||||
C.vips_thread_shutdown()
|
C.vips_thread_shutdown()
|
||||||
// clean image memory buffer?
|
|
||||||
return errors.New(s)
|
return errors.New(s)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
vips.h
12
vips.h
|
|
@ -91,6 +91,18 @@ vips_enum_nick_bridge(VipsImage *image) {
|
||||||
return vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type);
|
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
|
int
|
||||||
vips_embed_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height, int extend)
|
vips_embed_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height, int extend)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue