#include "ruby_vips.h"
#include "image.h"
#include "mask.h"
#include "header.h"
static ID id_black, id_extend, id_repeat, id_mirror, id_white;
/*
* call-seq:
* im.to_mask -> mask
*
* Make a mask from an image. All images are cast to band format :DOUBLE
* before processing. There are two cases for handling bands:
*
* If the image has a single band, Image#to_mask will write a mask the same
* size as the image.
*
* If the image has more than one band, it must be one pixel high or wide. In
* this case the output mask uses that axis to represent band values.
*/
VALUE
img_to_mask(VALUE obj)
{
vipsMask *msk;
VALUE mask = mask_alloc(cVIPSMask);
GetImg(obj, data, im);
Data_Get_Struct(mask, vipsMask, msk);
if (!(msk->dmask = im_vips2mask(im, "img_vips2mask")))
vips_lib_error();
return mask;
}
/*
* call-seq:
* im.dup -> image
*
* Create a copy of an image.
*/
VALUE
img_dup(VALUE obj)
{
RUBY_VIPS_UNARY(im_copy);
}
/*
* call-seq:
* im.copy_swap -> image
*
* Copy an image, swapping byte order between little and big endian. This
* really does change image pixels and does not just alter the header.
*/
VALUE
img_copy_swap(VALUE obj)
{
RUBY_VIPS_UNARY(im_copy_swap);
}
/*
* call-seq:
* im.copy_native -> image
*
* Copy an image to native order, that is, the order for the executing program.
*/
VALUE
img_copy_native(VALUE obj, VALUE input_msb_first)
{
#if IM_MAJOR_VERSION > 7 || IM_MINOR_VERSION >= 22
vipsImg *in, *out;
int msb_first;
VALUE new = img_spawn(obj);
Data_Get_Struct(obj, vipsImg, in);
Data_Get_Struct(new, vipsImg, out);
msb_first = input_msb_first == Qtrue ? TRUE : FALSE;
if( im_copy_native(in->in, out->in, msb_first) )
vips_lib_error();
return new;
#else
rb_raise(eVIPSError, "Your version of vips doesn't support this operation");
#endif
}
/*
* call-seq:
* im.copy_file -> image
*
* Copy an image to a disc file, then copy again to output. If the image is
* already a disc file, just copy straight through.
*
* The file is automatically deleted when image is closed.
*/
VALUE
img_copy_file(VALUE obj)
{
vipsImg *in, *out;
VALUE new = img_spawn(obj);
Data_Get_Struct(obj, vipsImg, in);
Data_Get_Struct(new, vipsImg, out);
if( im_copy_file(in->in, out->in) )
vips_lib_error();
return new;
}
/*
* call-seq:
* im.clip2fmt(fmt_symbol) -> image
*
* Convert the image to fmt_symbol format. You can convert between any
* pair of formats. Floats are truncated (not rounded). Out of range values are
* clipped.
*
* fmt_symbol can be any of: :NOTSET, :UCHAR, :CHAR, :USHORT, :SHORT,
* :UINT, :INT, :FLOAT, :COMPLEX, :DOUBLE, :DPCOMPLEX.
*/
VALUE
img_clip2fmt(VALUE obj, VALUE fmt)
{
GetImg(obj, data, im);
OutImg(obj, new, data_new, im_new);
if (im_clip2fmt(im, im_new, header_id_to_band_fmt(SYM2ID(fmt))))
vips_lib_error();
return new;
}
/*
* call-seq:
* im.scale -> image
*
* Search the image for the maximum and minimum value, then return the image
* as unsigned 8-bit, scaled so that the maximum value is 255 and the
* minimum is zero.
*/
VALUE
img_scale(VALUE obj)
{
RUBY_VIPS_UNARY(im_scale);
}
/*
* call-seq:
* im.msb([band]) -> image
*
* Turn any integer image to 8-bit unsigned char by discarding all but the most
* significant byte. Signed values are converted to unsigned by adding 128.
*
* This operator also works for LABQ coding.
*
* If band is given, this will turn any integer image to a single-band'
* 8-bit unsigned char by discarding all but the most significant byte from the
* selected band.
*/
VALUE
img_msb(int argc, VALUE *argv, VALUE obj)
{
VALUE v_num;
GetImg(obj, data, im);
OutImg(obj, new, data_new, im_new);
rb_scan_args(argc, argv, "01", &v_num);
if (NIL_P(v_num)) {
if (im_msb(im, im_new))
vips_lib_error();
} else if (im_msb_band(im, im_new, NUM2INT(v_num)))
vips_lib_error();
return new;
}
/*
* call-seq:
* im.c2amph -> image
*
* Convert a complex image from rectangular to polar coordinates. Angles are
* expressed in degrees.
*/
VALUE
img_c2amph(VALUE obj)
{
RUBY_VIPS_UNARY(im_c2amph);
}
/*
* call-seq:
* im.c2rect -> image
*
* Convert a complex image from polar to rectangular coordinates. Angles are
* expressed in degrees.
*/
VALUE
img_c2rect(VALUE obj)
{
RUBY_VIPS_UNARY(im_c2rect);
}
/*
* call-seq:
* im.ri2c(other_image) -> image
*
* Compose two real images to make a complex image. If either image has band
* format :DOUBLE, the output image is band format :DPCOMPLEX. Otherwise the
* output image is :COMPLEX.
*
* If the number of bands differs, one of the images must have one band. In
* this case, an n-band image is formed from the one-band image by joining n
* copies of the one-band image together, and then the two n-band images are
* operated upon.
*
* The two input images are cast up to the smallest common type.
*/
VALUE
img_ri2c(VALUE obj, VALUE obj2)
{
RUBY_VIPS_BINARY(im_ri2c);
}
/*
* call-seq:
* im.c2imag -> image
*
* Extract the imaginary part of a complex image.
*/
VALUE
img_c2imag(VALUE obj)
{
RUBY_VIPS_UNARY(im_c2imag);
}
/*
* call-seq:
* im.c2real -> image
*
* Extract the real part of a complex image.
*/
VALUE
img_c2real(VALUE obj)
{
RUBY_VIPS_UNARY(im_c2real);
}
/*
* call-seq:
* im.scaleps -> image
*
* Scale a power spectrum. Transform with log10(1.0 + x ** 0.25)) + 0.5,
* then scale so max == 255.
*/
VALUE
img_scaleps(VALUE obj)
{
RUBY_VIPS_UNARY(im_scaleps);
}
/*
* call-seq:
* im.falsecolour -> image
*
* Turn a 1-band 8-bit image into a 3-band 8-bit image with a false colour
* map. The map is supposed to make small differences in brightness more
* obvious.
*/
VALUE
img_falsecolour(VALUE obj)
{
RUBY_VIPS_UNARY(im_falsecolour);
}
/*
* call-seq:
* Image.gaussnoise(width, height, mean, sigma) -> image
*
* Make a one band float image of gaussian noise with the specified
* distribution. The noise distribution is created by averaging 12 random
* numbers with the appropriate weights.
*/
VALUE
img_s_gaussnoise(VALUE obj, VALUE x, VALUE y, VALUE mean, VALUE sigma)
{
OutPartial(new, data, im_new);
if (im_gaussnoise(im_new, NUM2INT(x), NUM2INT(y), NUM2DBL(mean),
NUM2DBL(sigma)))
vips_lib_error();
return new;
}
/*
* call-seq:
* Image.black(width, height, bands) -> image
*
* Make a black unsigned char image of a specified size.
*/
VALUE
img_s_black(VALUE obj, VALUE width, VALUE height, VALUE bands)
{
OutPartial(new, data, im_new);
if (im_black(im_new, NUM2INT(width), NUM2INT(height), NUM2DBL(bands)))
vips_lib_error();
return new;
}
/*
* call-seq:
* Image.text(text, font, width, alignment, dpi) -> image
*
* Draw the string text to an image. @out is a one-band 8-bit
* unsigned char image, with 0 for no text and 255 for text. Values inbetween
* are used for anti-aliasing.
*
* text is the text to render as a UTF-8 string. It can contain Pango
* markup, for example "<i>The</i>Guardian".
*
* font is the font to render with, selected by fontconfig. Examples
* might be "sans 12" or perhaps "bitstream charter bold 10".
*
* width is the maximum number of pixels across to draw within. If the
* generated text is wider than this, it will wrap to a new line. In this
* case, alignment can be used to set the alignment style for multi-line
* text. 0 means left-align, 1 centre, 2 right-align.
*
* dpi sets the resolution to render at. "sans 12" at 72 dpi draws
* characters approximately 12 pixels high.
*/
VALUE
img_s_text(VALUE obj, VALUE text, VALUE font, VALUE width,
VALUE alignment, VALUE dpi)
{
OutPartial(new, data, im_new);
if (im_text(im_new, StringValuePtr(text), StringValuePtr(font),
NUM2INT(width), NUM2INT(alignment), NUM2INT(dpi)))
vips_lib_error();
return new;
}
/*
* call-seq:
* im.extract_band(band [,num_bands]) -> image
*
* Extract the band given by band from the image. If num_bands is
* given, extract that many bands. Bands number from zero.
*
* im.extract_band(0, 2) # => new image with bands 0 & 1 from original.
*
* Extracting bands outside the input image will trigger an error.
*/
VALUE
img_extract_band(int argc, VALUE *argv, VALUE obj)
{
VALUE v_start_band, v_num_bands;
int num_bands;
GetImg(obj, data, im);
OutImg(obj, new, data_new, im_new);
rb_scan_args(argc, argv, "11", &v_start_band, &v_num_bands);
num_bands = NIL_P(v_num_bands) ? 1 : NUM2INT(v_num_bands);
if (im_extract_bands(im, im_new, NUM2INT(v_start_band), num_bands))
vips_lib_error();
return new;
}
/*
* call-seq:
* im.extract_area(left, top, width, height) -> image
* im.extract_area(left, top, width, height, band [,num_bands]) -> image
*
* The first form will extract an area from an image.
*
* The second form will extract an area and a number of bands from an image.
* Bands number from zero. Extracting outside the input image will trigger an
* error.
*/
VALUE
img_extract_area(int argc, VALUE *argv, VALUE obj)
{
VALUE v_left, v_top, v_width, v_height, v_start_band, v_num_bands;
int start_band, num_bands;
GetImg(obj, data, im);
OutImg(obj, new, data_new, im_new);
rb_scan_args(argc, argv, "42", &v_left, &v_top, &v_width, &v_height,
&v_start_band, &v_num_bands);
/* Start band defaults to the first band */
start_band = NIL_P(v_start_band) ? 0 : NUM2INT(v_start_band);
/* Number of bands defaults to all bands or the selected band.
*/
if (NIL_P(v_num_bands))
num_bands = NIL_P(v_start_band) ? im->Bands : 1;
else
num_bands = NUM2INT(v_num_bands);
if (im_extract_areabands(im, im_new, NUM2INT(v_left), NUM2INT(v_top),
NUM2INT(v_width), NUM2INT(v_height), start_band, num_bands))
vips_lib_error();
return new;
}
/*
* call-seq:
* im.embed(type_sym, x, y, width, height) -> image
*
* The opposite of Image#extract: embed an image within a larger image.
* type_sym controls what appears in the new pixels:
*
* * :black - black pels (all bytes == 0)
* * :extend - extend pels from image edge
* * :repeat - repeat image
* * :mirror - mirror image
* * :white - white pixels (all bytes == 255)
*/
VALUE
img_embed(VALUE obj, VALUE type_v, VALUE x, VALUE y, VALUE width, VALUE height)
{
ID type_id = SYM2ID(type_v);
int type;
GetImg(obj, data, im);
OutImg(obj, new, data_new, im_new);
if (type_id == id_black) type = 0;
else if (type_id == id_extend) type = 1;
else if (type_id == id_repeat) type = 2;
else if (type_id == id_mirror) type = 3;
else if (type_id == id_white) type = 4;
else
rb_raise(rb_eArgError, "Extend type must be :black, :extend, :repeat, :mirror, :white");
if (im_embed(im, im_new, type, NUM2INT(x), NUM2INT(y), NUM2INT(width),
NUM2INT(height)))
vips_lib_error();
return new;
}
/*
* call-seq:
* im.tile_cache(tile_width, tile_height, max_tiles) -> image
*
* This operation behaves rather like copy between images,
* except that it keeps a cache of computed pixels.
* This cache is made of up to max_tiles tiles (a value of -1 for
* means any number of tiles), and each tile is of size tile_width
* by tile_height pixels.
*/
VALUE
img_tile_cache(VALUE obj, VALUE tile_width, VALUE tile_height, VALUE max_tiles)
{
GetImg(obj, data, im);
OutImg(obj, new, data_new, im_new);
if (im_tile_cache(im, im_new,
NUM2INT(tile_width), NUM2INT(tile_height), NUM2INT(max_tiles)))
vips_lib_error();
return new;
}
/*
* call-seq:
* im.bandjoin(other_image, ...) -> image
*
* Join a set of images together, bandwise. If the images have n and m bands,
* then the output image will have n + m bands, with the first n coming from
* the first image and the last m from the second.
*
* The images must be the same size. The input images are cast up to the
* smallest common type.
*/
VALUE
img_bandjoin(int argc, VALUE *argv, VALUE obj)
{
vipsImg *im_t;
IMAGE **ins;
int i;
GetImg(obj, data, im);
OutImg(obj, new, data_new, im_new);
if(argc == 0)
rb_raise(rb_eArgError, "Need at least one argument");
ins = IM_ARRAY(im_new, argc + 1, IMAGE*);
ins[0] = im;
for (i = 0; i < argc; i++) {
img_add_dep(data_new, argv[i]);
Data_Get_Struct(argv[i], vipsImg, im_t);
ins[i + 1] = im_t->in;
}
if (im_gbandjoin(ins, im_new, argc + 1))
vips_lib_error();
return new;
}
/*
* call-seq:
* im.insert_noexpand(other_image, x, y) -> image
*
* Insert one image into another. other_image is inserted into *self* at
* position x, y relative to the top LH corner of *self*. The
* output image is the same size as *self*. other_image is clipped
* against the edges of *self*.
*
* If the number of bands differs, one of the images must have one band. In
* this case, an n-band image is formed from the one-band image by joining n
* copies of the one-band image together, and then the two n-band images are
* operated upon.
*
* The two input images are cast up to the smallest common type.
*/
VALUE
img_insert_noexpand(VALUE obj, VALUE obj2, VALUE x, VALUE y)
{
GetImg(obj, data, im);
GetImg(obj2, data2, im2);
OutImg2(obj, obj2, new, data_new, im_new);
if (im_insert_noexpand(im, im2, im_new, NUM2INT(x), NUM2INT(y)))
vips_lib_error();
return new;
}
static VALUE
img_insert_one(VALUE obj, VALUE obj2, VALUE x, VALUE y)
{
GetImg(obj, data, im);
GetImg(obj2, data2, im2);
OutImg2(obj, obj2, new, data_new, im_new);
if (im_insert(im, im2, im_new, NUM2INT(x), NUM2INT(y)))
vips_lib_error();
return new;
}
static VALUE
img_insertset(int argc, VALUE *argv, VALUE obj, VALUE obj2)
{
int i, *x, *y;
GetImg(obj, data, im);
GetImg(obj2, data2, im2);
OutImg2(obj, obj2, new, data_new, im_new);
x = IM_ARRAY(im_new, argc - 1, int);
y = IM_ARRAY(im_new, argc - 1, int);
for(i = 1; i < argc; i++) {
x[i] = NUM2INT(RARRAY_PTR(argv[i])[0]);
y[i] = NUM2INT(RARRAY_PTR(argv[i])[1]);
}
if( im_insertset(im, im2, im_new, argc - 1, x, y) )
vips_lib_error();
return new;
}
/*
* call-seq:
* im.insert(other_image, x, y) -> image
* im.insert(other_image, coordinates, ...) -> image
*
* Insert other_image repeatedly into *self* at the positions listed in
* the coordinates arrays. These arrays should contain x and y
* coordinates:
*
* im.insert(other_image, 100, 100) # => other_image inserted once at 100, 100
* im.insert(other_image, [12, 20], [32, 22]) # => inserted twice at given coordinates
* The output image is the same size as *self*. other_image is clipped
* against the edges of *self*.
*
* This operation is fast for large @n, but will use a memory buffer the size
* of the output image. It's useful for things like making scatter plots.
*
* If the number of bands differs, one of the images must have one band. In
* this case, an n-band image is formed from the one-band image by joining n
* copies of the one-band image together, and then the two n-band images are
* operated upon.
*
* The two input images are cast up to the smallest common type.
*/
VALUE
img_insert(int argc, VALUE *argv, VALUE obj)
{
VALUE obj2, x_or_ary, y_or_arys;
rb_scan_args(argc, argv, "2*", &obj2, &x_or_ary, &y_or_arys);
if (TYPE(x_or_ary) == T_ARRAY) {
if (!NIL_P(y_or_arys))
rb_ary_unshift(y_or_arys, x_or_ary);
return img_insertset(RARRAY_LEN(y_or_arys), RARRAY_PTR(y_or_arys), obj,
obj2);
}
if (RARRAY_LEN(y_or_arys) != 1)
rb_raise(rb_eArgError,
"Need two coordinates or an array of coordinates");
return img_insert_one(obj, obj2, x_or_ary, RARRAY_PTR(y_or_arys)[0]);
}
/*
* call-seq:
* im.lrjoin(other_image) -> image
*
* Join *self* and other_image together, left-right. If one is taller
* than the other, the output image will be as high as the smaller image.
*
* If the number of bands differs, one of the images must have one band. In
* this case, an n-band image is formed from the one-band image by joining n
* copies of the one-band image together, and then the two n-band images are
* operated upon.
*
* The two input images are cast up to the smallest common type.
*/
VALUE
img_lrjoin(VALUE obj, VALUE obj2)
{
RUBY_VIPS_BINARY(im_lrjoin);
}
/*
* call-seq:
* im.tbjoin(other_image) -> image
*
* Join *self* and other_image together, top-bottom. If one is taller
* than the other, the output image will be as high as the smaller image.
*
* If the number of bands differs, one of the images must have one band. In
* this case, an n-band image is formed from the one-band image by joining n
* copies of the one-band image together, and then the two n-band images are
* operated upon.
*
* The two input images are cast up to the smallest common type.
*/
VALUE
img_tbjoin(VALUE obj, VALUE obj2)
{
RUBY_VIPS_BINARY(im_tbjoin);
}
/*
* call-seq:
* im.replicate(across, down) -> image
*
* Replicate an image across times horizontally down times
* vertically.
*/
VALUE
img_replicate(VALUE obj, VALUE across, VALUE down)
{
GetImg(obj, data, im);
OutImg(obj, new, data_new, im_new);
if (im_replicate(im, im_new, NUM2INT(across), NUM2INT(down)))
vips_lib_error();
return new;
}
/*
* call-seq:
* im.grid(tile_height, across, down) -> image
*
* Chop a tall thin image up into a set of tiles, lay the tiles out in a grid.
*
* *self* should be a very tall, thin image containing a list of smaller
* images. Volumetric or time-sequence images are often laid out like this.
* This image is chopped into a series of tiles, each tile_height pixels'
* high and the width of *self*. The tiles are then rearranged into a grid
* across tiles across and down tiles down in row-major order.
*/
VALUE
img_grid(VALUE obj, VALUE tile_height, VALUE across, VALUE down)
{
GetImg(obj, data, im);
OutImg(obj, new, data_new, im_new);
if (im_grid(im, im_new, NUM2INT(tile_height), NUM2INT(across),
NUM2INT(down)))
vips_lib_error();
return new;
}
/*
* call-seq:
* im.wrap(x, y) -> image
*
* Slice an image up and move the segments about so that the pixel that was
* at 0, 0 is now at x, y.
*/
VALUE
img_wrap(VALUE obj, VALUE x, VALUE y)
{
GetImg(obj, data, im);
OutImg(obj, new, data_new, im_new);
if (im_wrap(im, im_new, NUM2INT(x), NUM2INT(y)))
vips_lib_error();
return new;
}
/*
* call-seq:
* im.fliphor -> image
*
* Flips an image left-right.
*/
VALUE
img_fliphor(VALUE obj)
{
RUBY_VIPS_UNARY(im_fliphor);
}
/*
* call-seq:
* im.flipver -> image
*
* Flips an image top-bottom.
*/
VALUE
img_flipver(VALUE obj)
{
RUBY_VIPS_UNARY(im_flipver);
}
/*
* call-seq:
* im.rot90 -> image
*
* Rotate an image 90 degrees.
*/
VALUE
img_rot90(VALUE obj)
{
RUBY_VIPS_UNARY(im_rot90);
}
/*
* call-seq:
* im.rot180 -> image
*
* Rotate an image 180 degrees.
*/
VALUE
img_rot180(VALUE obj)
{
RUBY_VIPS_UNARY(im_rot180);
}
/*
* call-seq:
* im.rot270 -> image
*
* Rotate an image 270 degrees.
*/
VALUE
img_rot270(VALUE obj)
{
RUBY_VIPS_UNARY(im_rot270);
}
/*
* call-seq:
* im.subsample(x [,y]) -> image
*
* Subsample an image by an integer fraction. This is fast nearest-neighbour
* shrink.
*
* If only one integer is given the width and height are subsampled equally.
*/
VALUE
img_subsample(int argc, VALUE *argv, VALUE obj)
{
VALUE x, y;
GetImg(obj, data, im);
OutImg(obj, new, data_new, im_new);
rb_scan_args(argc, argv, "11", &x, &y);
if (NIL_P(y))
y = x;
if (im_subsample(im, im_new, NUM2INT(x), NUM2INT(y)))
vips_lib_error();
return new;
}
/*
* call-seq:
* im.zoom(x [,y]) -> image
*
* Zoom an image by repeating pixels. This is fast nearest-neighbour
* zoom.
*
* If only one integer is given the width and height are zoomed equally.
*/
VALUE
img_zoom(int argc, VALUE *argv, VALUE obj)
{
VALUE x, y;
GetImg(obj, data, im);
OutImg(obj, new, data_new, im_new);
rb_scan_args(argc, argv, "11", &x, &y);
if (NIL_P(y))
y = x;
if (im_zoom(im, im_new, NUM2INT(x), NUM2INT(y)))
vips_lib_error();
return new;
}
void
init_Image_conversion()
{
id_black = rb_intern("black");
id_extend = rb_intern("extend");
id_repeat = rb_intern("repeat");
id_mirror = rb_intern("mirror");
id_white = rb_intern("white");
}