diff --git a/vips/conversion.c b/vips/conversion.c index 9fbf111b..743ae94c 100644 --- a/vips/conversion.c +++ b/vips/conversion.c @@ -126,6 +126,10 @@ int flip_image(VipsImage *in, VipsImage **out, int direction) { return vips_flip(in, out, direction, NULL); } +int recomb_image(VipsImage *in, VipsImage **out, VipsImage *m) { + return vips_recomb(in, out, m, NULL); +} + int extract_image_area(VipsImage *in, VipsImage **out, int left, int top, int width, int height) { return vips_extract_area(in, out, left, top, width, height, NULL); diff --git a/vips/conversion.go b/vips/conversion.go index 7ad5d19b..42f1d466 100644 --- a/vips/conversion.go +++ b/vips/conversion.go @@ -212,6 +212,18 @@ func vipsFlip(in *C.VipsImage, direction Direction) (*C.VipsImage, error) { return out, nil } +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-recomb +func vipsRecomb(in *C.VipsImage, m *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("recomb") + var out *C.VipsImage + + if err := C.recomb_image(in, &out, m); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + // https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-extract-area func vipsExtractArea(in *C.VipsImage, left, top, width, height int) (*C.VipsImage, error) { incOpCounter("extractArea") diff --git a/vips/conversion.h b/vips/conversion.h index cfc9f110..e9f51bc5 100644 --- a/vips/conversion.h +++ b/vips/conversion.h @@ -20,6 +20,8 @@ int embed_multi_page_image_background(VipsImage *in, VipsImage **out, int left, int flip_image(VipsImage *in, VipsImage **out, int direction); +int recomb_image(VipsImage *in, VipsImage **out, VipsImage *m); + int extract_image_area(VipsImage *in, VipsImage **out, int left, int top, int width, int height); int extract_area_multi_page(VipsImage *in, VipsImage **out, int left, int top, diff --git a/vips/image.go b/vips/image.go index fa6cd0c4..f20fcead 100644 --- a/vips/image.go +++ b/vips/image.go @@ -1774,6 +1774,55 @@ func (r *ImageRef) Flip(direction Direction) error { return nil } +// Recomb recombines the image bands using the matrix provided +func (r *ImageRef) Recomb(matrix [][]float64) error { + numBands := r.Bands() + // Ensure the provided matrix is 3x3 + if len(matrix) != 3 || len(matrix[0]) != 3 || len(matrix[1]) != 3 || len(matrix[2]) != 3 { + return errors.New("Invalid recombination matrix") + } + // If the image is RGBA, expand the matrix to 4x4 + if numBands == 4 { + matrix = append(matrix, []float64{0, 0, 0, 1}) + for i := 0; i < 3; i++ { + matrix[i] = append(matrix[i], 0) + } + } else if numBands != 3 { + return errors.New("Unsupported number of bands") + } + + // Flatten the matrix + var matrixValues []float64 + for _, row := range matrix { + for _, value := range row { + matrixValues = append(matrixValues, value) + } + } + + // Convert the Go slice to a C array and get its size + matrixPtr := unsafe.Pointer(&matrixValues[0]) + matrixSize := C.size_t(len(matrixValues) * 8) // 8 bytes for each float64 + + // Create a VipsImage from the matrix in memory + matrixImage := C.vips_image_new_from_memory(matrixPtr, matrixSize, C.int(numBands), C.int(numBands), 1, C.VIPS_FORMAT_DOUBLE) + + // Check for any Vips errors + errMsg := C.GoString(C.vips_error_buffer()) + if errMsg != "" { + C.vips_error_clear() + return errors.New("Vips error: " + errMsg) + } + + // Recombine the image using the matrix + out, err := vipsRecomb(r.image, matrixImage) + if err != nil { + return err + } + + r.setImage(out) + return nil +} + // Rotate rotates the image by multiples of 90 degrees. To rotate by arbitrary angles use Similarity. func (r *ImageRef) Rotate(angle Angle) error { width := r.Width() diff --git a/vips/image_test.go b/vips/image_test.go index 36f5157b..95cedb0d 100644 --- a/vips/image_test.go +++ b/vips/image_test.go @@ -672,6 +672,38 @@ func TestImageRef_CompositeMulti(t *testing.T) { require.NoError(t, err) } +func TestImageRef_Recomb(t *testing.T) { + Startup(nil) + + image, err := NewImageFromFile(resources + "png-24bit.png") + require.NoError(t, err) + + matrix := [][]float64{ + {0.3588, 0.7044, 0.1368}, + {0.2990, 0.5870, 0.1140}, + {0.2392, 0.4696, 0.0912}, + } + + err = image.Recomb(matrix) + require.NoError(t, err) +} + +func TestImageRef_Recomb_Error(t *testing.T) { + Startup(nil) + + image, err := NewImageFromFile(resources + "png-24bit.png") + require.NoError(t, err) + + matrix := [][]float64{ + {0.3588, 0.7044, 0.1368, 0}, + {0.2990, 0.5870, 0.1140, 0}, + {0.2392, 0.4696, 0.0912, 0}, + } + + err = image.Recomb(matrix) + require.Error(t, err) +} + func TestCopy(t *testing.T) { Startup(nil) diff --git a/vips/label.c b/vips/label.c index 01de8134..2e27d3c7 100644 --- a/vips/label.c +++ b/vips/label.c @@ -7,9 +7,15 @@ int text(VipsImage **out, const char *text, const char *font, int width, } int label(VipsImage *in, VipsImage **out, LabelOptions *o) { - double ones[3] = {1, 1, 1}; + int input_bands = vips_image_get_bands(in); + double *ones = (double *)malloc(sizeof(double) * input_bands); + for (int i = 0; i < input_bands; i++) { + ones[i] = 1.0; + } + VipsImage *base = vips_image_new(); VipsImage **t = (VipsImage **)vips_object_local_array(VIPS_OBJECT(base), 9); + if (vips_text(&t[0], o->Text, "font", o->Font, "width", o->Width, "height", o->Height, "align", o->Align, NULL) || vips_linear1(t[0], &t[1], o->Opacity, 0.0, NULL) || @@ -17,21 +23,28 @@ int label(VipsImage *in, VipsImage **out, LabelOptions *o) { vips_embed(t[2], &t[3], o->OffsetX, o->OffsetY, t[2]->Xsize + o->OffsetX, t[2]->Ysize + o->OffsetY, NULL)) { g_object_unref(base); + free(ones); return 1; } + if (vips_black(&t[4], 1, 1, NULL) || - vips_linear(t[4], &t[5], ones, o->Color, 3, NULL) || + vips_linear(t[4], &t[5], ones, o->Color, input_bands, NULL) || vips_cast(t[5], &t[6], VIPS_FORMAT_UCHAR, NULL) || vips_copy(t[6], &t[7], "interpretation", in->Type, NULL) || vips_embed(t[7], &t[8], 0, 0, in->Xsize, in->Ysize, "extend", VIPS_EXTEND_COPY, NULL)) { g_object_unref(base); + free(ones); return 1; } + if (vips_ifthenelse(t[3], t[8], in, out, "blend", TRUE, NULL)) { g_object_unref(base); + free(ones); return 1; } + g_object_unref(base); + free(ones); return 0; }