Skip to content

Commit

Permalink
Add some functions (#464)
Browse files Browse the repository at this point in the history
* add arithmetic functions

* add functions which get background color

* add/revise vips_text function

* add Min function

---------

Co-authored-by: steve.3282 <[email protected]>
  • Loading branch information
Flash-nDie and steve.3282 authored Feb 27, 2025
1 parent 8d9c549 commit 0d82957
Show file tree
Hide file tree
Showing 14 changed files with 316 additions and 11 deletions.
16 changes: 16 additions & 0 deletions vips/arithmetic.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,19 @@ int hist_norm(VipsImage *in, VipsImage **out) {
int hist_entropy(VipsImage *in, double *out) {
return vips_hist_entropy(in, out, NULL);
}

int subtract(VipsImage *in1, VipsImage *in2, VipsImage **out) {
return vips_subtract(in1, in2, out, NULL);
}

int absOp(VipsImage *img, VipsImage **out) {
return vips_abs(img, out, NULL);
}

int project(VipsImage *in, VipsImage **col, VipsImage **row) {
return vips_project(in, col, row, NULL);
}

int minOp(VipsImage *in, double *out, int *x, int *y, int size) {
return vips_min(in, out, "x", x, "y", y, "size", size, NULL);
}
47 changes: 47 additions & 0 deletions vips/arithmetic.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,50 @@ func vipsHistEntropy(in *C.VipsImage) (float64, error) {

return float64(out), nil
}

// https://www.libvips.org/API/current/libvips-arithmetic.html#vips-subtract
func vipsSubtract(in1 *C.VipsImage, in2 *C.VipsImage) (*C.VipsImage, error) {
incOpCounter("subtract")
var out *C.VipsImage

if err := C.subtract(in1, in2, &out); err != 0 {
return nil, handleImageError(out)
}

return out, nil
}

// https://www.libvips.org/API/current/libvips-arithmetic.html#vips-abs
func vipsAbs(img *C.VipsImage) (*C.VipsImage, error) {
incOpCounter("abs")
var out *C.VipsImage
if err := C.absOp(img, &out); err != 0 {
return nil, handleImageError(out)
}

return out, nil
}

// https://www.libvips.org/API/current/libvips-arithmetic.html#vips-project
func vipsProject(in *C.VipsImage) (*C.VipsImage, *C.VipsImage, error) {
incOpCounter("project")
var col, row *C.VipsImage

if err := C.project(in, &col, &row); err != 0 {
return nil, nil, handleVipsError()
}
return col, row, nil
}

// https://www.libvips.org/API/current/libvips-arithmetic.html#vips-min
func vipsMin(in *C.VipsImage) (float64, int, int, error) {
incOpCounter("min")
var out C.double
var x, y C.int

if err := C.minOp(in, &out, &x, &y, C.int(1)); err != 0 {
return 0, 0, 0, handleVipsError()
}

return float64(out), int(x), int(y), nil
}
4 changes: 4 additions & 0 deletions vips/arithmetic.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ int hist_find(VipsImage *in, VipsImage **out);
int hist_cum(VipsImage *in, VipsImage **out);
int hist_norm(VipsImage *in, VipsImage **out);
int hist_entropy(VipsImage *in, double *out);
int subtract(VipsImage *in1, VipsImage *in2, VipsImage **out);
int absOp(VipsImage *img, VipsImage **out);
int project(VipsImage *in, VipsImage **col, VipsImage **row);
int minOp(VipsImage *in, double *out, int *x, int *y, int size);
6 changes: 6 additions & 0 deletions vips/create.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@ int identity(VipsImage **out, int ushort) {
return vips_identity(out, NULL);
}
}

// https://libvips.github.io/libvips/API/current/libvips-create.html#vips-text
int text(VipsImage **out, TextOptions *o) {
return vips_text(out, o->Text, "font", o->Font, "width", o->Width, "height", o->Height, "align", o->Align,
"dpi", o->DPI, "rgba", o->RGBA, "justify", o->Justify, "spacing", o->Spacing, "wrap", o->Wrap, NULL);
}
74 changes: 74 additions & 0 deletions vips/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,35 @@ package vips

// #include "create.h"
import "C"
import "unsafe"

type TextWrap int

type TextParams struct {
Text string
Font string
Width int
Height int
Alignment Align
DPI int
RGBA bool
Justify bool
Spacing int
Wrap TextWrap
}

type vipsTextOptions struct {
Text *C.char
Font *C.char
Width C.int
Height C.int
DPI C.int
RGBA C.gboolean
Justify C.gboolean
Spacing C.int
Alignment C.VipsAlign
Wrap C.VipsTextWrap
}

// https://libvips.github.io/libvips/API/current/libvips-create.html#vips-xyz
func vipsXYZ(width int, height int) (*C.VipsImage, error) {
Expand Down Expand Up @@ -35,3 +64,48 @@ func vipsIdentity(ushort bool) (*C.VipsImage, error) {

return out, nil
}

// TextWrap enum
const (
TextWrapWord TextWrap = C.VIPS_TEXT_WRAP_WORD
TextWrapChar TextWrap = C.VIPS_TEXT_WRAP_CHAR
TextWrapWordChar TextWrap = C.VIPS_TEXT_WRAP_WORD_CHAR
TextWrapNone TextWrap = C.VIPS_TEXT_WRAP_NONE
)

// https://libvips.github.io/libvips/API/current/libvips-create.html#vips-text
func vipsText(params *TextParams) (*C.VipsImage, error) {
var out *C.VipsImage

text := C.CString(params.Text)
defer freeCString(text)

font := C.CString(params.Font)
defer freeCString(font)

opts := vipsTextOptions{
Text: text,
Font: font,
Width: C.int(params.Width),
Height: C.int(params.Height),
DPI: C.int(params.DPI),
Alignment: C.VipsAlign(params.Alignment),
Spacing: C.int(params.Spacing),
Wrap: C.VipsTextWrap(params.Wrap),
}

if params.RGBA {
opts.RGBA = C.TRUE
}

if params.Justify {
opts.Justify = C.TRUE
}

err := C.text(&out, (*C.TextOptions)(unsafe.Pointer(&opts)))
if err != 0 {
return nil, handleImageError(out)
}

return out, nil
}
13 changes: 13 additions & 0 deletions vips/create.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,20 @@
#include <vips/vips.h>
#include <vips/foreign.h>
// clang-format on
typedef struct {
const char *Text;
const char *Font;
int Width;
int Height;
int DPI;
gboolean RGBA;
gboolean Justify;
int Spacing;
VipsAlign Align;
VipsTextWrap Wrap;
} TextOptions;

int xyz(VipsImage **out, int width, int height);
int black(VipsImage **out, int width, int height);
int identity(VipsImage **out, int ushort);
int text(VipsImage **out, TextOptions *o);
6 changes: 5 additions & 1 deletion vips/header.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ int get_meta_loader(const VipsImage *in, const char **out) {
return vips_image_get_string(in, VIPS_META_LOADER, out);
}

int get_background(VipsImage *in, double **out, int *n) {
return vips_image_get_array_double(in, "background", out, n);
}

int get_image_delay(VipsImage *in, int **out) {
return vips_image_get_array_int(in, "delay", out, NULL);
}
Expand Down Expand Up @@ -119,4 +123,4 @@ unsigned long image_get_blob(VipsImage *in, const char *name, const void **data,
}

return 0;
}
}
12 changes: 12 additions & 0 deletions vips/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,18 @@ func vipsImageSetDelay(in *C.VipsImage, data []C.int) error {
return nil
}

func vipsImageGetBackground(in *C.VipsImage) ([]float64, error) {
incOpCounter("imageGetBackground")
var out *C.double
var n C.int
defer gFreePointer(unsafe.Pointer(out))

if err := C.get_background(in, &out, &n); err != 0 {
return nil, handleVipsError()
}
return fromCArrayDouble(out, int(n)), nil
}

// vipsDetermineImageTypeFromMetaLoader determine the image type from vips-loader metadata
func vipsDetermineImageTypeFromMetaLoader(in *C.VipsImage) ImageType {
vipsLoader, ok := vipsImageGetMetaLoader(in)
Expand Down
3 changes: 2 additions & 1 deletion vips/header.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ void set_page_height(VipsImage *in, int height);
int get_meta_loader(const VipsImage *in, const char **out);
int get_image_delay(VipsImage *in, int **out);
void set_image_delay(VipsImage *in, const int *array, int n);
int get_background(VipsImage *in, double **out, int *n);

void image_set_blob(VipsImage *in, const char *name, const void *data,
size_t dataLength);
Expand All @@ -38,4 +39,4 @@ void image_set_double(VipsImage *in, const char *name, double i);
unsigned long image_get_double(VipsImage *in, const char *name, double *out);

void image_set_int(VipsImage *in, const char *name, int i);
unsigned long image_get_int(VipsImage *in, const char *name, int *out);
unsigned long image_get_int(VipsImage *in, const char *name, int *out);
52 changes: 52 additions & 0 deletions vips/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,12 @@ func Black(width, height int) (*ImageRef, error) {
return imageRef, err
}

// Text draws the string text to an image.
func Text(params *TextParams) (*ImageRef, error) {
img, err := vipsText(params)
return newImageRef(img, ImageTypeUnknown, ImageTypeUnknown, nil), err
}

func newImageRef(vipsImage *C.VipsImage, currentFormat ImageType, originalFormat ImageType, buf []byte) *ImageRef {
imageRef := &ImageRef{
image: vipsImage,
Expand Down Expand Up @@ -827,6 +833,15 @@ func (r *ImageRef) SetPageDelay(delay []int) error {
return vipsImageSetDelay(r.image, data)
}

// Background get the background of image.
func (r *ImageRef) Background() ([]float64, error) {
out, err := vipsImageGetBackground(r.image)
if err != nil {
return nil, err
}
return out, nil
}

// Export creates a byte array of the image for use.
// The function returns a byte array that can be written to a file e.g. via os.WriteFile().
// N.B. govips does not currently have built-in support for directly exporting to a file.
Expand Down Expand Up @@ -1777,6 +1792,43 @@ func (r *ImageRef) DrawRect(ink ColorRGBA, left int, top int, width int, height
return nil
}

// Subtract calculate subtract operation between two images.
func (r *ImageRef) Subtract(in2 *ImageRef) error {
out, err := vipsSubtract(r.image, in2.image)
if err != nil {
return err
}

r.setImage(out)
return nil
}

// Abs calculate abs operation.
func (r *ImageRef) Abs() error {
out, err := vipsAbs(r.image)
if err != nil {
return err
}

r.setImage(out)
return nil
}

// Project calculate project operation.
func (r *ImageRef) Project() (*ImageRef, *ImageRef, error) {
col, row, err := vipsProject(r.image)
if err != nil {
return nil, nil, err
}

return newImageRef(col, r.format, r.originalFormat, nil), newImageRef(row, r.format, r.originalFormat, nil), nil
}

// Min finds the minimum value in an image.
func (r *ImageRef) Min() (float64, int, int, error) {
return vipsMin(r.image)
}

// Rank does rank filtering on an image. A window of size width by height is passed over the image.
// At each position, the pixels inside the window are sorted into ascending order and the pixel at position
// index is output. index numbers from 0.
Expand Down
72 changes: 72 additions & 0 deletions vips/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,78 @@ func Test_NewImageFromFile(t *testing.T) {
assert.Equal(t, 1, image.Pages())
}

func TestImageRef_ArithmeticOperation(t *testing.T) {
Startup(nil)

image, err := NewImageFromFile(resources + "png-24bit.png")
require.NoError(t, err)
image2, err := NewImageFromFile(resources + "png-24bit.png")
require.NoError(t, err)

orgWidth := image.Width()
orgHeight := image.Height()

_, _, _, err = image.Min()
require.NoError(t, err)

err = image2.Abs()
require.NoError(t, err)

err = image.Subtract(image2)
require.NoError(t, err)

colImage, rowImage, err := image.Project()
require.NoError(t, err)

require.Equal(t, orgWidth, colImage.Width())
require.Equal(t, 1, colImage.Height())
require.Equal(t, 1, rowImage.Width())
require.Equal(t, orgHeight, rowImage.Height())
}

func TestImageRef_Background(t *testing.T) {
Startup(nil)
image, err := NewImageFromFile(resources + "gif-animated.gif")
require.NoError(t, err)

background, err := image.Background()
require.NoError(t, err)

require.Equal(t, 3, len(background))
}

func Test_MakeTextImage(t *testing.T) {
Startup(nil)

textImage, err := Text(&TextParams{
Text: "Test",
Font: "Helvetica",
Width: 10,
Height: 10,
Alignment: AlignLow,
DPI: 72,
Wrap: TextWrapWord,
})
require.NoError(t, err)
require.NotNil(t, textImage)

// pango Image
pangoText := "<span font_desc='Helvetica' font_size='13pt' foreground='black'>Test</span>"
pangoTextImage, err := Text(&TextParams{
Text: pangoText,
Width: 10,
Height: 0,
Alignment: AlignLow,
DPI: 72,
RGBA: true,
Justify: false,
Spacing: 0,
Wrap: TextWrapWord,
})
require.NoError(t, err)
require.NotNil(t, pangoTextImage)
}

// TODO unit tests to cover:
// NewImageFromReader failing test
// NewImageFromFile failing test
Expand Down
Loading

0 comments on commit 0d82957

Please sign in to comment.