mirror of
https://github.com/talgo-cloud/bimg.git
synced 2026-03-07 21:48:13 -08: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
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
17
image.go
17
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}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
56
resize.go
56
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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
44
vips.go
44
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)
|
||||
}
|
||||
|
|
|
|||
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);
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue