├── .editorconfig ├── .github └── workflows │ ├── ci.yml │ └── doc.yml ├── .gitignore ├── LICENSE ├── README.md ├── example ├── annotate.cr ├── canny.cr ├── captcha_generator.cr ├── combine.cr ├── embed.cr ├── emboss.cr ├── helloworld.cr ├── image_pyramid.cr ├── leak_test.cr ├── mutable_image.cr ├── sobel.cr ├── thumbnail.cr └── watermark.cr ├── scripts ├── enums.ecr ├── gen_enums.cr ├── gen_ext.cr ├── image.ecr ├── init.cr └── mutableimage.ecr ├── shard.yml ├── spec ├── connection_spec.cr ├── gvalue_spec.cr ├── image_spec.cr ├── mutableimage_spec.cr ├── region_spec.cr ├── samples │ ├── alpha.png │ ├── balloon.v │ ├── ghost.ppm │ ├── huge.jpg │ ├── icc.jpg │ ├── lcd.icc │ ├── lion.svg │ ├── no_alpha.png │ ├── sample.csv │ ├── sample.exr │ ├── wagon.jpg │ └── wagon.v ├── spec_helper.cr └── vips_spec.cr └── src ├── vips.cr └── vips ├── cache.cr ├── connection.cr ├── enums.cr ├── ext ├── image.cr └── mutableimage.cr ├── gobject.cr ├── gvalue.cr ├── image.cr ├── interpolate.cr ├── introspect.cr ├── lib.cr ├── mutableimage.cr ├── operation.cr ├── region.cr ├── source.cr ├── stats.cr ├── target.cr ├── vips.cr ├── vipsblob.cr └── vipsobject.cr /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.cr] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: crystal-vips CI 2 | on: 3 | push: 4 | pull_request: 5 | branches: [main] 6 | schedule: 7 | - cron: '0 6 * * 6' # Every Saturday 6 AM 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Download source 13 | uses: actions/checkout@v2 14 | - name: Install Crystal 15 | uses: crystal-lang/install-crystal@v1 16 | - name: Install libvips 17 | env: 18 | DEBIAN_FRONTEND: noninteractive 19 | run: 20 | # we only need the library 21 | sudo apt-get update && sudo apt-get install libvips-dev 22 | - name: Run tests 23 | run: crystal spec --error-trace -v spec 24 | - name: Check formatting 25 | run: crystal tool format --check 26 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Download source 13 | uses: actions/checkout@v2 14 | - name: Install Crystal 15 | uses: crystal-lang/install-crystal@v1 16 | - name: Build 17 | run: crystal docs 18 | - name: Deploy 19 | if: github.ref == 'refs/heads/main' 20 | uses: JamesIves/github-pages-deploy-action@3.7.1 21 | with: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | BRANCH: gh-pages 24 | FOLDER: docs 25 | CLEAN: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /docs/ 2 | /lib/ 3 | /bin/ 4 | /images/ 5 | /.shards/ 6 | *.dwarf 7 | .vscode/** 8 | # Libraries don't need dependency lock 9 | # Dependencies will be locked in applications that use them 10 | /shard.lock 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ali Naqvi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CrystalVips 2 | 3 | [![crystal-vips CI](https://github.com/naqvis/crystal-vips/actions/workflows/ci.yml/badge.svg)](https://github.com/naqvis/crystal-vips/actions/workflows/ci.yml) 4 | [![Latest release](https://img.shields.io/github/release/naqvis/crystal-vips.svg)](https://github.com/naqvis/crystal-vips/releases) 5 | [![Docs](https://img.shields.io/badge/docs-available-brightgreen.svg)](https://naqvis.github.io/crystal-vips/) 6 | 7 | Provides Crystal language interface to the [libvips](https://github.com/libvips/libvips) image processing library. 8 | Programs that use `CrystalVips` don't manipulate images directly, instead they create pipelines of image processing operations starting from a source image. When the pipe is connected to a destination, the whole pipeline executes at once and in parallel, streaming the image from source to destination in a set of small fragments. 9 | 10 | Because `CrystalVips` is parallel, its' quick, and because it doesn't need to keep entire images in memory, its light. For example, the libvips speed and memory use benchmark: 11 | 12 | [https://github.com/libvips/libvips/wiki/Speed-and-memory-use](https://github.com/libvips/libvips/wiki/Speed-and-memory-use) 13 | 14 | ## Pre-requisites 15 | 16 | You need to [install the libvips 17 | library](https://www.libvips.org/install.html). It's in the linux package managers, homebrew and MacPorts, and there are Windows binaries on the vips website. For example, on Debian: 18 | 19 | ``` 20 | sudo apt-get install --no-install-recommends libvips42 21 | ``` 22 | 23 | (`--no-install-recommends` stops Debian installing a *lot* of extra packages) 24 | 25 | Or macOS: 26 | 27 | ``` 28 | brew install vips 29 | ``` 30 | 31 | ## Installation 32 | 33 | 1. Add the dependency to your `shard.yml`: 34 | 35 | ```yaml 36 | dependencies: 37 | vips: 38 | github: naqvis/crystal-vips 39 | ``` 40 | 41 | 2. Run `shards install` 42 | 43 | ## Usage 44 | 45 | ```crystal 46 | require "vips" 47 | 48 | im = Vips::Image.new_from_file("image.jpg") 49 | 50 | # put im at position (100, 100) in a 3000 x 3000 pixel image, 51 | # make the other pixels in the image by mirroring im up / down / 52 | # left / right, see 53 | # https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-embed 54 | im = im.embed(100, 100, 3000, 3000, extend: Vips::Enums::Extend::Mirror) 55 | 56 | # multiply the green (middle) band by 2, leave the other two alone 57 | im *= [1, 2, 1] 58 | 59 | # make an image from an array constant, convolve with it 60 | mask = Vips::Image.new_from_array([ 61 | [-1, -1, -1], 62 | [-1, 16, -1], 63 | [-1, -1, -1]], 8) 64 | im = im.conv(mask, precision: Vips::Enums::Precision::Integer) 65 | 66 | # finally, write the result back to a file on disk 67 | im.write_to_file("output.jpg") 68 | ``` 69 | 70 | Refer to [example](example) folder for more samples 71 | 72 | ## Development 73 | 74 | To run all tests: 75 | 76 | ``` 77 | crystal spec 78 | ``` 79 | 80 | # Getting more help 81 | 82 | The libvips website has a handy table of [all the libvips 83 | operators](http://libvips.github.io/libvips/API/current/func-list.html). Each 84 | one links to the main API docs so you can see what you need to pass to it. 85 | 86 | A simple way to see the arguments for an operation is to try running it 87 | from the command-line. For example: 88 | 89 | ```bash 90 | $ vips embed 91 | embed an image in a larger image 92 | usage: 93 | embed in out x y width height 94 | where: 95 | in - Input image, input VipsImage 96 | out - Output image, output VipsImage 97 | x - Left edge of input in output, input gint 98 | default: 0 99 | min: -1000000000, max: 1000000000 100 | y - Top edge of input in output, input gint 101 | default: 0 102 | min: -1000000000, max: 1000000000 103 | width - Image width in pixels, input gint 104 | default: 1 105 | min: 1, max: 1000000000 106 | height - Image height in pixels, input gint 107 | default: 1 108 | min: 1, max: 1000000000 109 | optional arguments: 110 | extend - How to generate the extra pixels, input VipsExtend 111 | default: black 112 | allowed: black, copy, repeat, mirror, white, background 113 | background - Color for background pixels, input VipsArrayDouble 114 | operation flags: sequential 115 | ``` 116 | 117 | ## Contributing 118 | 119 | 1. Fork it () 120 | 2. Create your feature branch (`git checkout -b my-new-feature`) 121 | 3. Commit your changes (`git commit -am 'Add some feature'`) 122 | 4. Push to the branch (`git push origin my-new-feature`) 123 | 5. Create a new Pull Request 124 | 125 | ## Contributors 126 | 127 | - [Ali Naqvi](https://github.com/naqvis) - creator and maintainer 128 | -------------------------------------------------------------------------------- /example/annotate.cr: -------------------------------------------------------------------------------- 1 | require "../src/vips" 2 | 3 | if ARGV.size < 2 4 | puts "Usage: #{__FILE__} input_image output" 5 | exit(1) 6 | end 7 | 8 | im = Vips::Image.new_from_file(ARGV[0], access: Vips::Enums::Access::Sequential) 9 | 10 | left_text, _ = Vips::Image.text("left corner", dpi: 300) 11 | left = left_text.embed(50, 50, im.width, 150) 12 | 13 | right_text, _ = Vips::Image.text("right corner", dpi: 300) 14 | right = right_text.embed(im.width - right_text.width - 50, 50, im.width, 150) 15 | 16 | footer = (left | right).ifthenelse(0, [255, 0, 0], blend: true) 17 | 18 | im = im.insert(footer, 0, im.height, expand: true) 19 | 20 | im.write_to_file ARGV[1] 21 | -------------------------------------------------------------------------------- /example/canny.cr: -------------------------------------------------------------------------------- 1 | require "../src/vips" 2 | 3 | # Edge detection 4 | 5 | if ARGV.size < 2 6 | puts "Usage: #{__FILE__} input_image output" 7 | exit(1) 8 | end 9 | 10 | im = Vips::Image.new_from_file(ARGV[0], access: Vips::Enums::Access::Sequential) 11 | 12 | # optionall convert to greyscale 13 | # mono = im.colourspace(Vips::Enums::Interpretation::Bw) 14 | # canny = mono.canny(sigma: 1.4, precision: Vips::Enums::Precision::Integer) 15 | 16 | # Canny edge detector 17 | canny = im.canny(sigma: 1.4, precision: Vips::Enums::Precision::Integer) 18 | 19 | # Canny makes a float image, scale the output up to make it visible 20 | scale = canny * 64 21 | 22 | scale.write_to_file(ARGV[1]) 23 | -------------------------------------------------------------------------------- /example/captcha_generator.cr: -------------------------------------------------------------------------------- 1 | require "../src/vips" 2 | 3 | # Captcha generator 4 | # Reference: https://github.com/libvips/libvips/issues/898 5 | 6 | def wobble(image) 7 | # a warp image is a 2D grid containing the new coordinates of each pixel with 8 | # the new x in band 0 and the new y in band 1 9 | # 10 | # you can also use a complex image 11 | # 12 | # start from a low-res XY image and distort it 13 | 14 | xy = Vips::Image.xyz(image.width // 20, image.height // 20) 15 | x_distort = Vips::Image.gaussnoise(xy.width, xy.height) 16 | y_distort = Vips::Image.gaussnoise(xy.width, xy.height) 17 | xy += (x_distort.bandjoin(y_distort) / 150) 18 | xy = xy.resize(20) 19 | xy *= 20 20 | 21 | # apply the warp 22 | image.mapim(xy) 23 | end 24 | 25 | if ARGV.size < 2 26 | puts "Usage: #{__FILE__} outputfile text" 27 | exit(1) 28 | end 29 | 30 | text_layer = Vips::Image.black 1, 1 31 | x_position = 0 32 | 33 | ARGV[1].each_char do |c| 34 | if c.ascii_whitespace? 35 | x_position += 50 36 | next 37 | end 38 | 39 | letter, _ = Vips::Image.text(c.to_s, dpi: 600) 40 | 41 | image = letter.gravity(Vips::Enums::CompassDirection::Centre, letter.width + 50, letter.height + 50) 42 | 43 | # random scale and rotate 44 | image = image.similarity(scale: Random.rand(0.2) + 0.8, 45 | angle: Random.rand(40) - 20) 46 | 47 | # random wobble 48 | image = wobble(image) 49 | 50 | # random color 51 | color = (1..3).map { Random.rand(255) } 52 | image = image.ifthenelse(color, 0, true) 53 | 54 | # tag as 9-bit srgb 55 | image = image.copy(interpretation: Vips::Enums::Interpretation::Srgb).cast(Vips::Enums::BandFormat::Uchar) 56 | 57 | # position at our write position in the image 58 | image = image.embed(x_position, 0, image.width + x_position, image.height) 59 | 60 | text_layer += image 61 | text_layer = text_layer.cast(Vips::Enums::BandFormat::Uchar) 62 | 63 | x_position += letter.width 64 | end 65 | 66 | # remove any unused edges 67 | text_layer = text_layer.crop(*text_layer.find_trim(background: 0)) 68 | 69 | # make an alpha for the text layer: just a mono version of the image, but scaled 70 | # up so letters themeselves are not transparent 71 | alpha = (text_layer.colourspace(Vips::Enums::Interpretation::Bw) * 3).cast(Vips::Enums::BandFormat::Uchar) 72 | text_layer = text_layer.bandjoin(alpha) 73 | 74 | # make a white background with random speckles 75 | speckles = Vips::Image.gaussnoise(text_layer.width, text_layer.height, mean: 400, sigma: 200) 76 | background = (1..3).reduce(speckles) do |a, _| 77 | a.bandjoin(Vips::Image.gaussnoise(text_layer.width, text_layer.height, mean: 400, sigma: 200)) 78 | .copy(interpretation: Vips::Enums::Interpretation::Srgb).cast(Vips::Enums::BandFormat::Uchar) 79 | end 80 | 81 | # composite the text over the background 82 | final = background.composite(text_layer, Vips::Enums::BlendMode::Over) 83 | final.write_to_file ARGV[0] 84 | -------------------------------------------------------------------------------- /example/combine.cr: -------------------------------------------------------------------------------- 1 | require "../src/vips" 2 | 3 | # Conversion 4 | # Reference: https://github.com/libvips/lua-vips/blob/master/example/combine.lua 5 | 6 | if ARGV.size < 3 7 | puts "Usage: #{__FILE__} input_image watermark_image output" 8 | exit(1) 9 | end 10 | 11 | LEFT = 100 12 | TOP = 100 13 | 14 | im = Vips::Image.new_from_file(ARGV[0], access: Vips::Enums::Access::Sequential) 15 | wm = Vips::Image.new_from_file(ARGV[1], access: Vips::Enums::Access::Sequential) 16 | 17 | width = wm.width 18 | height = wm.height 19 | 20 | # extract related area from main image 21 | base = im.crop(LEFT, TOP, width, height) 22 | 23 | # composite the two areas using the PDF "over" mode 24 | composite = base.composite(wm, Vips::Enums::BlendMode::Over) 25 | 26 | # the result will have an alpha, and our base image does not .. we must flatten 27 | # out the alpha before we can insert it back into a plain RGB JPG image 28 | rgb = composite.flatten 29 | 30 | # insert composite back in to main image on related area 31 | combined = im.insert(rgb, LEFT, TOP) 32 | combined.write_to_file(ARGV[2]) 33 | -------------------------------------------------------------------------------- /example/embed.cr: -------------------------------------------------------------------------------- 1 | require "../src/vips" 2 | 3 | # Embed / Multiply / Convolution 4 | 5 | if ARGV.size < 2 6 | puts "Usage: #{__FILE__} input_image output" 7 | exit(1) 8 | end 9 | 10 | im = Vips::Image.new_from_file(ARGV[0]) 11 | 12 | # put im at position (100, 100) in a 3000 x 3000 pixel image, 13 | # make the other pixels in the image by mirroring im up / down / 14 | # left / right, see 15 | # https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-embed 16 | im = im.embed(100, 100, 3000, 3000, extend: Vips::Enums::Extend::Mirror) 17 | 18 | # multiply the green (middle) band by 2, leave the other two alone 19 | im *= [1, 2, 1] 20 | 21 | # make an image from an array constant, convolve with it 22 | mask = Vips::Image.new_from_array([ 23 | [-1, -1, -1], 24 | [-1, 16, -1], 25 | [-1, -1, -1], 26 | ], 8) 27 | im = im.conv(mask, precision: Vips::Enums::Precision::Integer) 28 | 29 | # finally, write the result back to a file on disk 30 | im.write_to_file(ARGV[1]) 31 | -------------------------------------------------------------------------------- /example/emboss.cr: -------------------------------------------------------------------------------- 1 | require "../src/vips" 2 | 3 | # Emboss 4 | 5 | if ARGV.size < 2 6 | puts "Usage: #{__FILE__} input_image output" 7 | exit(1) 8 | end 9 | 10 | im = Vips::Image.new_from_file(ARGV[0]) 11 | 12 | kernel1 = Vips::Image.new_from_array([ 13 | [0, 1, 0], 14 | [0, 0, 0], 15 | [0, -1, 0], 16 | ], offset: 128) 17 | 18 | kernel2 = Vips::Image.new_from_array([ 19 | [1, 0, 0], 20 | [0, 0, 0], 21 | [0, 0, -1], 22 | ], offset: 128) 23 | 24 | kernel3 = kernel1.rot270 25 | kernel4 = kernel2.rot90 26 | 27 | # Apply the emboss kernels 28 | conv1 = im.conv(kernel1, precision: Vips::Enums::Precision::Float) 29 | conv2 = im.conv(kernel2, precision: Vips::Enums::Precision::Float) 30 | conv3 = im.conv(kernel3, precision: Vips::Enums::Precision::Float) 31 | conv4 = im.conv(kernel4, precision: Vips::Enums::Precision::Float) 32 | 33 | images = [conv1, conv2, conv3, conv4] 34 | joined = Vips::Image.arrayjoin(images, across: 2) 35 | joined.write_to_file(ARGV[1]) 36 | -------------------------------------------------------------------------------- /example/helloworld.cr: -------------------------------------------------------------------------------- 1 | require "../src/vips" 2 | 3 | # Conversion 4 | # Reference: https://github.com/libvips/lua-vips/blob/master/example/hello-world.lua 5 | 6 | if ARGV.size < 1 7 | puts "Usage: #{__FILE__} output_image" 8 | exit(1) 9 | end 10 | 11 | image, _ = Vips::Image.text("Hello World!", dpi: 300) 12 | image.write_to_file(ARGV[0]) 13 | -------------------------------------------------------------------------------- /example/image_pyramid.cr: -------------------------------------------------------------------------------- 1 | require "../src/vips" 2 | 3 | # Image Pyramid 4 | 5 | if ARGV.size < 2 6 | puts "Usage: #{__FILE__} input_image output" 7 | exit(1) 8 | end 9 | 10 | TileSize = 50 11 | 12 | im = Vips::Image.new_from_file(ARGV[0], access: Vips::Enums::Access::Sequential) 13 | test = im.replicate(TileSize, TileSize) 14 | 15 | im.set_progress { |v| puts "#{v}% complete" } 16 | test.dzsave(ARGV[1]) 17 | -------------------------------------------------------------------------------- /example/leak_test.cr: -------------------------------------------------------------------------------- 1 | require "../src/vips" 2 | 3 | # Leak test 4 | 5 | if ARGV.size < 1 6 | puts "Usage: #{__FILE__} input_image" 7 | exit(1) 8 | end 9 | 10 | # Load from memory buffer 10000 times. 11 | Vips.leak = true 12 | Vips::Cache.max = 0 13 | 14 | data = File.read(ARGV[0]).to_slice 15 | 16 | (1..10000).each { |_| 17 | img = Vips::Image.new_from_buffer(data) 18 | puts "memory processing #{img}" 19 | } 20 | -------------------------------------------------------------------------------- /example/mutable_image.cr: -------------------------------------------------------------------------------- 1 | require "../src/vips" 2 | 3 | # Leak test 4 | 5 | if ARGV.size < 1 6 | puts "Usage: #{__FILE__} output_image" 7 | exit(1) 8 | end 9 | 10 | im = Vips::Image.black(500, 500) 11 | im = im.mutate do |x| 12 | (0..1).step 0.01 do |i| 13 | x.draw_line([255.0], (x.width * i).to_i, 0, 0, (x.height * (1 - i)).to_i) 14 | end 15 | end 16 | im.write_to_file(ARGV[0]) 17 | -------------------------------------------------------------------------------- /example/sobel.cr: -------------------------------------------------------------------------------- 1 | require "../src/vips" 2 | 3 | # Edge detection 4 | 5 | if ARGV.size < 2 6 | puts "Usage: #{__FILE__} input_image output" 7 | exit(1) 8 | end 9 | 10 | im = Vips::Image.new_from_file(ARGV[0], access: Vips::Enums::Access::Sequential) 11 | 12 | # optionall, convert to greyscale 13 | # mono = im.colourspace(Vips::Enums::Interpretation::Bw) 14 | 15 | # Apply sobel operator 16 | sobel = im.sobel 17 | 18 | sobel.write_to_file(ARGV[1]) 19 | -------------------------------------------------------------------------------- /example/thumbnail.cr: -------------------------------------------------------------------------------- 1 | require "../src/vips" 2 | 3 | # Thumbnail 4 | 5 | if ARGV.size < 2 6 | puts "Usage: #{__FILE__} input_image output" 7 | exit(1) 8 | end 9 | 10 | image = Vips::Image.thumbnail(ARGV[0], 300, height: 300) 11 | image.write_to_file(ARGV[1]) 12 | -------------------------------------------------------------------------------- /example/watermark.cr: -------------------------------------------------------------------------------- 1 | require "../src/vips" 2 | 3 | # Water mark 4 | 5 | if ARGV.size < 3 6 | puts "Usage: #{__FILE__} input_image output watermark_text" 7 | exit(1) 8 | end 9 | 10 | im = Vips::Image.new_from_file(ARGV[0], access: Vips::Enums::Access::Sequential) 11 | 12 | # make the text mask 13 | text, _ = Vips::Image.text(ARGV[2], width: 200, dpi: 200, font: "sans bold") 14 | text = text.rotate(-45) 15 | # make the text transparent 16 | text = (text * 0.3).cast(Vips::Enums::BandFormat::Uchar) 17 | text = text.gravity :centre, 200, 200 18 | text = text.replicate(1 + im.width // text.width, 1 + im.height // text.height) 19 | text = text.crop(0, 0, im.width, im.height) 20 | 21 | # we make a constant colour image and attach the text mask as the alpha 22 | overlay = (text.new_from_image([255, 128, 128])).copy(interpretation: Vips::Enums::Interpretation::Srgb) 23 | overlay = overlay.bandjoin(text) 24 | 25 | # overlay the text 26 | im = im.composite(overlay, Vips::Enums::BlendMode::Over) 27 | 28 | im.write_to_file ARGV[1] 29 | -------------------------------------------------------------------------------- /scripts/enums.ecr: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by running: 2 | # 3 | # scripts/gen_enums.cr 4 | # 5 | # libvips version: <%= Vips.version(0) %>.<%= Vips.version(1) %>.<%= Vips.version(2) %> 6 | # 7 | # DO NOT EDIT 8 | # 9 | # Changes to this file may cause incorrect behavior and will be lost if the code is regenerated 10 | # 11 | 12 | module Vips::Enums 13 | 14 | <%= enums_str %> 15 | 16 | end -------------------------------------------------------------------------------- /scripts/gen_enums.cr: -------------------------------------------------------------------------------- 1 | require "ecr" 2 | require "compiler/crystal/formatter" 3 | require "../src/vips" 4 | require "./init" 5 | 6 | enums_str = String.build do |result| 7 | Vips.get_enums.each do |name| 8 | next if name.starts_with?("Gsf") 9 | gtype = Vips.type_from_name(name) 10 | vals = Vips.enum_values(gtype) 11 | result << " enum " << (name.starts_with?("Vips") ? name[4..] : name) << "\n" 12 | vals.each do |k, v| 13 | result << k.gsub('-', '_').camelcase << " = " << v << "\n" 14 | end 15 | result << "end\n\n" 16 | end 17 | end 18 | 19 | output = String.build do |str| 20 | ECR.embed "#{__DIR__}/enums.ecr", str 21 | end 22 | output = Crystal.format(output) 23 | 24 | File.write("#{__DIR__}/enums.cr", output) 25 | -------------------------------------------------------------------------------- /scripts/gen_ext.cr: -------------------------------------------------------------------------------- 1 | # This script generates the files src/vips/ext/image.cr and src/vips/ext/mutableimage.cr 2 | require "ecr" 3 | require "compiler/crystal/formatter" 4 | require "../src/vips" 5 | require "./init" 6 | 7 | G_to_Crystal = { 8 | Vips::GValue::GBool => "Bool", 9 | Vips::GValue::GInt => "Int32", 10 | Vips::GValue::GUint64 => "UInt32", 11 | Vips::GValue::GDouble => "Float64", 12 | Vips::GValue::GString => "String", 13 | Vips::GValue::GObject => "GObject", 14 | Vips::GValue::VImageType => "Image", 15 | Vips::GValue::VArrayInt => "Array(Int32)", 16 | Vips::GValue::VArrayDouble => "Array(Float64)", 17 | Vips::GValue::VArrayImage => "Array(Image)", 18 | Vips::GValue::VRefStr => "String", 19 | Vips::GValue::VBlob => "Bytes", 20 | Vips::GValue::VSource => "Source", 21 | Vips::GValue::VTarget => "Target", 22 | Vips.type_from_name("VipsInterpolate") => "Interpolate", 23 | Vips.type_from_name("VipsFailOn") => "Enums::FailOn", 24 | Vips.type_from_name("VipsAccess") => "Enums::Access", 25 | Vips.type_from_name("VipsAlign") => "Enums::Align", 26 | Vips.type_from_name("VipsAngle") => "Enums::Angle", 27 | Vips.type_from_name("VipsAngle45") => "Enums::Angle45", 28 | Vips.type_from_name("VipsBandFormat") => "Enums::BandFormat", 29 | Vips.type_from_name("VipsBlendMode") => "Enums::BlendMode", 30 | Vips.type_from_name("VipsCoding") => "Enums::Coding", 31 | Vips.type_from_name("VipsCombine") => "Enums::Combine", 32 | Vips.type_from_name("VipsCombineMode") => "Enums::CombineMode", 33 | Vips.type_from_name("VipsCompassDirection") => "Enums::CompassDirection", 34 | Vips.type_from_name("VipsDemandStyle") => "Enums::DemandStyle", 35 | Vips.type_from_name("VipsDirection") => "Enums::Direction", 36 | Vips.type_from_name("VipsExtend") => "Enums::Extend", 37 | Vips.type_from_name("VipsForeignDzContainer") => "Enums::ForeignDzContainer", 38 | Vips.type_from_name("VipsForeignDzDepth") => "Enums::ForeignDzDepth", 39 | Vips.type_from_name("VipsForeignDzLayout") => "Enums::ForeignDzLayout", 40 | Vips.type_from_name("VipsForeignHeifCompression") => "Enums::ForeignHeifCompression", 41 | Vips.type_from_name("VipsForeignPpmFormat") => "Enums::ForeignPpmFormat", 42 | Vips.type_from_name("VipsForeignSubsample") => "Enums::ForeignSubsample", 43 | Vips.type_from_name("VipsForeignTiffCompression") => "Enums::ForeignTiffCompression", 44 | Vips.type_from_name("VipsForeignTiffPredictor") => "Enums::ForeignTiffPredictor", 45 | Vips.type_from_name("VipsForeignTiffResunit") => "Enums::ForeignTiffResunit", 46 | Vips.type_from_name("VipsForeignWebpPreset") => "Enums::ForeignWebpPreset", 47 | Vips.type_from_name("VipsIntent") => "Enums::Intent", 48 | Vips.type_from_name("VipsInteresting") => "Enums::Interesting", 49 | Vips.type_from_name("VipsInterpretation") => "Enums::Interpretation", 50 | Vips.type_from_name("VipsKernel") => "Enums::Kernel", 51 | Vips.type_from_name("VipsOperationBoolean") => "Enums::OperationBoolean", 52 | Vips.type_from_name("VipsOperationComplex") => "Enums::OperationComplex", 53 | Vips.type_from_name("VipsOperationComplex2") => "Enums::OperationComplex2", 54 | Vips.type_from_name("VipsOperationComplexget") => "Enums::OperationComplexget", 55 | Vips.type_from_name("VipsOperationMath") => "Enums::OperationMath", 56 | Vips.type_from_name("VipsOperationMath2") => "Enums::OperationMath2", 57 | Vips.type_from_name("VipsOperationMorphology") => "Enums::OperationMorphology", 58 | Vips.type_from_name("VipsOperationRelational") => "Enums::OperationRelational", 59 | Vips.type_from_name("VipsOperationRound") => "Enums::OperationRound", 60 | Vips.type_from_name("VipsPCS") => "Enums::PCS", 61 | Vips.type_from_name("VipsPrecision") => "Enums::Precision", 62 | Vips.type_from_name("VipsRegionShrink") => "Enums::RegionShrink", 63 | Vips.type_from_name("VipsSize") => "Enums::Size", 64 | Vips.type_from_name("VipsForeignFlags") => "Enums::ForeignFlags", 65 | Vips.type_from_name("VipsForeignPngFilter") => "Enums::ForeignPngFilter", 66 | 67 | } 68 | 69 | all_nick_names = Vips.get_operations 70 | 71 | excludes = [ 72 | "scale", 73 | "ifthenelse", 74 | "bandjoin", 75 | "bandrank", 76 | "composite", 77 | "case", 78 | ] 79 | 80 | def g2c(name, gtype) : String 81 | if v = G_to_Crystal[gtype]? 82 | return v 83 | end 84 | fundamental = Vips.fundamental_type(gtype) 85 | if v = G_to_Crystal[fundamental]? 86 | return v 87 | end 88 | raise "Unsupported type: #{Vips.typename(gtype)} name: #{name}" 89 | end 90 | 91 | def safe_cast(type : String) 92 | if type.starts_with?("Enums::") 93 | return "as_enum(#{type})" 94 | end 95 | 96 | case type 97 | when "GObject" then "as_o" 98 | when "Image" then "as_image" 99 | when "Array(Int32)" then "as_a32" 100 | when "Array(Float64)" then "as_a64" 101 | when "Array(Image)" then "as_aimg" 102 | when "Bytes" then "as_bytes" 103 | when "Bool" then "as_b" 104 | when "Int32" then "as_i32" 105 | when "Float64" then "as_f64" 106 | when "String" then "as_s" 107 | when "UInt64" then "as_u64" 108 | else 109 | raise "#{type} not supported" 110 | end 111 | end 112 | 113 | def generate_props 114 | result = String::Builder.new 115 | tmpfile = Vips::Image.new_temp_file("%s.v") 116 | all_props = tmpfile.get_fields 117 | all_props.each do |prop| 118 | type = g2c(prop, tmpfile.get_typeof(prop)) 119 | type_str = "get(\"#{prop}\").#{safe_cast(type)}" 120 | result << "# " << tmpfile.get_blurb(prop) << "\n" 121 | result << "def " << prop.underscore << " : " << type << "\n" 122 | result << type_str 123 | result << "\nend\n" 124 | result << "\n" 125 | end 126 | result.to_s 127 | end 128 | 129 | def generate_method(opname, mutable = false) # , out_params = Array(Vips::Introspect::Argument).new) 130 | op = Vips::Operation.new(opname) 131 | intro = Vips::Introspect.get(opname) 132 | 133 | # we are only interested in non-deprecated args 134 | optional_input = intro.optional_input.values.select { |arg| (arg.flags & LibVips::VipsArgumentFlags::Deprecated).value == 0 } 135 | optional_output = intro.optional_output.values.select { |arg| (arg.flags & LibVips::VipsArgumentFlags::Deprecated).value == 0 } 136 | 137 | required_input = intro.required_input 138 | required_output = intro.required_output 139 | 140 | raise "Cannot generate #{opname} as this is a #{intro.mutable ? "" : "non-"}mutable operation." if (mutable ^ intro.mutable) 141 | 142 | reserved_keywords = %w[out case in] 143 | 144 | safe_identifier = ->(name : String) { reserved_keywords.includes?(name) ? "#{name}_" : name } 145 | safe_cast = ->(type : String) { 146 | case type 147 | when "GObject" then "as_o" 148 | when "Image" then "as_image" 149 | when "Array(Int32)" then "as_a32" 150 | when "Array(Float64)" then "as_a64" 151 | when "Array(Image)" then "as_aimg" 152 | when "Bytes" then "as_bytes" 153 | when "Bool" then "as_b" 154 | when "Int32" then "as_i32" 155 | when "Float64" then "as_f64" 156 | when "String" then "as_s" 157 | when "UInt64" then "as_u64" 158 | end 159 | } 160 | 161 | new_op_name = opname.underscore 162 | 163 | result = String::Builder.new 164 | description = op.get_description 165 | result << "# " << description.capitalize << '\n' 166 | result << " # 167 | #```crystal 168 | #" 169 | 170 | if required_output.size == 1 171 | arg = required_output.first 172 | result << "# " << safe_identifier.call(arg.name).underscore 173 | elsif required_output.size > 1 174 | result << "# output " 175 | elsif intro.mutable && intro.member_x? 176 | result << "# image.mutate { |x| " 177 | end 178 | 179 | if optional_output.size > 0 && !intro.mutable 180 | result << ", " << optional_output.reduce([] of String) { |arr, arg| arr << safe_identifier.call(arg.name).underscore }.join(", ") 181 | end 182 | 183 | result << " = " if required_output.size > 0 || (optional_output.size > 0 && !intro.mutable) 184 | 185 | if (mx = intro.member_x?) 186 | result << (intro.mutable ? "x" : mx.name) 187 | else 188 | result << "Vips::Image" 189 | end 190 | 191 | result << "." << new_op_name << "(" 192 | result << required_input.reduce([] of String) { |arr, arg| arr << safe_identifier.call(arg.name).underscore }.join(", ") 193 | 194 | if optional_input.size > 0 195 | result << ", " if required_input.size > 0 196 | result << "{" 197 | optional_input.each_with_index do |arg, i| 198 | result << safe_identifier.call(arg.name).underscore << ": " << g2c(arg.name, arg.type) 199 | result << ", " unless i == optional_input.size - 1 200 | end 201 | result << "}" 202 | end 203 | 204 | result << " }" if intro.mutable 205 | result << ")" unless intro.mutable 206 | result << "\n" << "# 207 | #``` 208 | # 209 | " 210 | if required_input.size > 0 || optional_input.size > 0 211 | result << "#\n# Input Parameters\n#\n" 212 | end 213 | 214 | result << "# **Required** \n#\n" if required_input.size > 0 215 | required_input.each do |arg| 216 | result << "# *" << arg.name.underscore << "* : " << g2c(arg.name, arg.type) << " - " << op.get_blurb(arg.name) << "\n#\n" 217 | end 218 | 219 | result << "# _Optionals_\n#\n" if optional_input.size > 0 220 | optional_input.each do |arg| 221 | result << "# *" << arg.name.underscore << "* : " << g2c(arg.name, arg.type) << " - " << op.get_blurb(arg.name) << "\n#\n" 222 | end 223 | 224 | get_type = ->(types : Array(String)) { 225 | case types.size 226 | when 0 then "Nil" 227 | when 1 then types.first 228 | else 229 | types.any? { |v| v != types.first } ? "Array(Type)" : "Array(#{types.first})" 230 | end 231 | } 232 | 233 | output_types = required_output.reduce([] of String) { |arr, arg| arr << g2c(arg.name, arg.type) } 234 | opt_outtypes = optional_output.reduce([] of String) { |arr, arg| arr << g2c(arg.name, arg.type) } 235 | 236 | output_type = get_type.call(output_types) 237 | opt_outtype = get_type.call(opt_outtypes) 238 | 239 | if required_output.size > 0 || (optional_output.size > 0 && !intro.mutable) 240 | result << "#\n# **Returns**\n#\n" 241 | required_output.each do |arg| 242 | result << "# " << op.get_blurb(arg.name) << "\n#\n" 243 | end 244 | result << "# _Optionals_\n#\n" if optional_output.size > 0 245 | optional_output.each do |arg| 246 | result << "# *" << arg.name.underscore << "* : " << g2c(arg.name, arg.type) << "? - " << op.get_blurb(arg.name) << "\n#\n" 247 | end 248 | end 249 | 250 | result << "def " 251 | result << "self." unless intro.member_x? 252 | result << new_op_name 253 | if required_input.size == 1 && optional_output.size == 0 && optional_input.size == 0 && required_input.first.type == Vips::GValue::VArrayImage 254 | result << "(*" << safe_identifier.call(required_input.first.name) << " : Image)\n" 255 | else 256 | result << "(" 257 | result << required_input.reduce([] of String) { |arr, arg| arr << safe_identifier.call(arg.name).underscore + " : " + g2c(arg.name, arg.type) }.join(", ") 258 | 259 | if optional_input.size > 0 260 | result << ", " if required_input.size > 0 261 | result << "**kwargs" 262 | end 263 | result << ")" 264 | end 265 | result << " : Nil" if intro.mutable 266 | result << "\n" 267 | if optional_input.size > 0 268 | result << "options = Optional.new(**kwargs)" << "\n" 269 | end 270 | 271 | if (required_output.size > 0 || optional_output.size > 0) && !intro.mutable 272 | if optional_input.size > 0 273 | optional_output.each do |arg| 274 | result << " options[\"" << arg.name << "\"] = true" 275 | result << "\n" 276 | end 277 | elsif optional_output.size > 0 278 | result << " optional_output = Optional.new(**{" 279 | optional_output.each_with_index do |arg, i| 280 | result << arg.name << ": " << "true" 281 | result << ", " unless i == optional_output.size - 1 282 | end 283 | result << "})" << "\n" 284 | end 285 | result << "\n" 286 | if required_output.size > 1 || optional_output.size > 0 287 | result << (required_output.size > 0 ? "results = " : "opts") 288 | end 289 | result << (intro.member_x? ? "self" : "Operation") 290 | result << ".call(\"" << opname << "\"" 291 | result << (optional_input.size > 0 ? ", options" : ", optional_output") if (optional_input.size > 0 || optional_output.size > 0) 292 | else 293 | result << (intro.member_x? ? "self" : "Operation") 294 | result << ".call(\"" << opname << "\"" 295 | result << ", options" if optional_input.size > 0 296 | end 297 | 298 | if required_input.size > 0 299 | result << ", " 300 | result << required_input.reduce([] of String) { |arr, arg| arr << safe_identifier.call(arg.name).underscore }.join(", ") 301 | end 302 | result << ")" 303 | 304 | unless intro.mutable 305 | if required_output.size > 0 && optional_output.size > 0 306 | result << ".as(Array(Type))" << "\n" 307 | result << "final_result = results.first" 308 | result << ".as(Type)." << safe_cast(output_type) 309 | elsif required_output.size > 1 310 | result << ".as(Array(Type))" << "\n" 311 | elsif required_output.size == 1 312 | result << ".as(Type)." << safe_cast(output_type) 313 | elsif optional_output.size > 0 314 | result << ".as(Type).as_h" 315 | end 316 | end 317 | result << "\n" 318 | 319 | unless intro.mutable 320 | if optional_output.size > 0 321 | if required_output.size > 0 322 | result << "\n" << "opts = results[1]?.try &.as_h" << "\n\n" 323 | end 324 | var_names = [] of String 325 | if output_type != "Nil" 326 | optional_output.each do |arg| 327 | name = safe_identifier.call(arg.name).underscore 328 | var_names << name 329 | arg_type = g2c(arg.name, arg.type) 330 | type_str = "val.#{safe_cast(arg_type)}" 331 | tmp_var = <<-VAR 332 | #{name} = ((o = opts) && (val = o["#{arg.name}"]?)) ? #{type_str} : nil 333 | VAR 334 | result << tmp_var << "\n" 335 | end 336 | result << ((required_output.size > 0) ? "{final_result," : "{") 337 | result << var_names.join(", ") << "}" 338 | end 339 | elsif required_output.size > 1 340 | var_names = [] of String 341 | required_output.each_with_index do |arg, i| 342 | arg_type = g2c(arg.name, arg.type) 343 | var_names << "results[#{i}].#{safe_cast(arg_type)}" 344 | end 345 | result << (var_names.size > 1 ? "{#{var_names.join(", ")}}" : var_names.first) 346 | result << "\n" 347 | end 348 | end 349 | result << "\nend" 350 | result << "\n" 351 | 352 | first_arg_type = required_input.size > 0 ? op.get_typeof(required_input.first.name) : -1 353 | if (first_arg_type == Vips::GValue::VSource) || (first_arg_type == Vips::GValue::VTarget) 354 | replace = first_arg_type == Vips::GValue::VSource ? "source" : "target" 355 | required_input = required_input.skip(1) 356 | old_opname = new_op_name 357 | new_op_name = new_op_name.gsub(replace, "stream") 358 | 359 | result << "\n# " << description.gsub(replace, "stream").capitalize << "\n#\n" 360 | result << "#```\n#" 361 | if required_output.size == 1 362 | arg = required_output.first 363 | result << "# " << safe_identifier.call(arg.name).underscore 364 | elsif required_output.size > 1 365 | result << "# output " 366 | end 367 | 368 | if optional_output.size > 0 369 | result << ", " << optional_output.reduce([] of String) { |arr, arg| arr << safe_identifier.call(arg.name).underscore }.join(", ") 370 | end 371 | 372 | result << " = " if required_output.size > 0 || optional_output.size > 0 373 | 374 | if (mx = intro.member_x?) 375 | result << (intro.mutable ? "x" : mx.name) 376 | else 377 | result << "Vips::Image" 378 | end 379 | 380 | result << "." << new_op_name << "(stream, " 381 | result << required_input.reduce([] of String) { |arr, arg| arr << safe_identifier.call(arg.name).underscore }.join(", ") 382 | 383 | if optional_input.size > 0 384 | result << ", " if required_input.size > 0 385 | result << "{" 386 | optional_input.each_with_index do |arg, i| 387 | result << safe_identifier.call(arg.name).underscore << ": " << g2c(arg.name, arg.type) 388 | result << ", " unless i == optional_input.size - 1 389 | end 390 | result << "}" 391 | end 392 | 393 | result << ")" << "\n" << "# 394 | #``` 395 | # 396 | " 397 | result << "# **Input Parameters**\n" << "#\n" << "# _Required_\n" 398 | result << "#\n# *stream* : IO - Stream to " << (first_arg_type == Vips::GValue::VSource ? "load from" : "save to") << "\n" 399 | 400 | required_input.each do |arg| 401 | result << "#\n# *" << arg.name.underscore << " : " << g2c(arg.name, arg.type) << "* - " << op.get_blurb(arg.name) << "\n" 402 | end 403 | 404 | result << "# _Optionals_\n" if optional_input.size > 0 405 | optional_input.each do |arg| 406 | result << "#\n# *" << arg.name.underscore << "* : " << g2c(arg.name, arg.type) << " - " << op.get_blurb(arg.name) << "\n" 407 | end 408 | 409 | output_types = required_output.reduce([] of String) { |arr, arg| arr << g2c(arg.name, arg.type) } 410 | output_type = case output_types.size 411 | when 0 then "Nil" 412 | when 1 then output_types.first 413 | else 414 | output_types.any? { |v| v != output_types.first } ? "Array(#{output_types.first})" : "Array(Type)" 415 | end 416 | 417 | if required_output.size > 0 || optional_output.size > 0 418 | result << "#\n# **Returns**\n" 419 | required_output.each do |arg| 420 | result << "#\n# *" << arg.name.underscore << "* : " << g2c(arg.name, arg.type) << " - " << op.get_blurb(arg.name) << "\n" 421 | end 422 | result << "# _Optionals_\n" 423 | optional_output.each do |arg| 424 | result << "#\n# *" << arg.name.underscore << "* : " << g2c(arg.name, arg.type) << "? - " << op.get_blurb(arg.name) << "\n" 425 | end 426 | end 427 | 428 | result << "def " 429 | result << "self." unless intro.member_x? 430 | result << new_op_name << "(stream : IO," 431 | result << required_input.reduce([] of String) { |arr, arg| arr << safe_identifier.call(arg.name).underscore + " : " + g2c(arg.name, arg.type) }.join(", ") 432 | 433 | if optional_input.size > 0 434 | result << ", " if required_input.size > 0 435 | result << "**kwargs" 436 | end 437 | result << ")\n" 438 | result << (first_arg_type == Vips::GValue::VSource ? "source = SourceStream.new_from_stream(stream)\n" : "target = TargetStream.new_from_stream(stream)\n") 439 | result << old_opname << "(" << (first_arg_type == Vips::GValue::VSource ? "source" : "target") 440 | result << ", " 441 | result << required_input.reduce([] of String) { |arr, arg| arr << safe_identifier.call(arg.name).underscore }.join(", ") 442 | if optional_input.size > 0 443 | result << ", " if required_input.size > 0 444 | result << "**kwargs" 445 | end 446 | result << ")\n" 447 | 448 | result << "\nend" 449 | result << "\n" 450 | end 451 | 452 | result.to_s 453 | end 454 | 455 | # get the list of all nicknames we can generate docstrings for. 456 | nick_names = (all_nick_names - excludes).map { |x| x.downcase } 457 | nick_names.sort! 458 | 459 | output = String.build do |str| 460 | ECR.embed "#{__DIR__}/image.ecr", str 461 | end 462 | output = Crystal.format(output) 463 | 464 | File.write("#{__DIR__}/../src/vips/ext/image.cr", output) 465 | 466 | output = String.build do |str| 467 | ECR.embed "#{__DIR__}/mutableimage.ecr", str 468 | end 469 | output = Crystal.format(output) 470 | 471 | File.write("#{__DIR__}/../src/vips/ext/mutableimage.cr", output) 472 | -------------------------------------------------------------------------------- /scripts/image.ecr: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by running: 2 | # 3 | # scripts/gen_ext.cr 4 | # 5 | # libvips version: <%= Vips.version(0) %>.<%= Vips.version(1) %>.<%= Vips.version(2) %> 6 | # 7 | # DO NOT EDIT 8 | # 9 | # Changes to this file may cause incorrect behavior and will be lost if the code is regenerated 10 | # 11 | 12 | module Vips 13 | 14 | class Image 15 | 16 | <%- nick_names.each do |nick| -%> 17 | <%= generate_method(nick) rescue nil %> 18 | <%- end -%> 19 | <%= generate_props %> 20 | end 21 | end -------------------------------------------------------------------------------- /scripts/init.cr: -------------------------------------------------------------------------------- 1 | def init 2 | LibVips.vips_object_get_type 3 | LibVips.vips_thing_get_type 4 | LibVips.vips_area_get_type 5 | LibVips.vips_save_string_get_type 6 | LibVips.vips_ref_string_get_type 7 | LibVips.vips_blob_get_type 8 | LibVips.vips_array_double_get_type 9 | LibVips.vips_array_int_get_type 10 | LibVips.vips_array_image_get_type 11 | LibVips.vips_connection_get_type 12 | LibVips.vips_source_get_type 13 | LibVips.vips_source_custom_get_type 14 | LibVips.vips_target_get_type 15 | LibVips.vips_target_custom_get_type 16 | LibVips.vips_sbuf_get_type 17 | LibVips.vips_image_get_type 18 | LibVips.vips_format_get_type 19 | LibVips.vips_region_get_type 20 | LibVips.vips_interpolate_get_type 21 | LibVips.vips_thread_state_get_type 22 | LibVips.vips_operation_get_type 23 | LibVips.vips_foreign_get_type 24 | LibVips.vips_foreign_load_get_type 25 | LibVips.vips_foreign_save_get_type 26 | LibVips.vips_operation_math_get_type 27 | LibVips.vips_operation_math2_get_type 28 | LibVips.vips_operation_round_get_type 29 | LibVips.vips_operation_relational_get_type 30 | LibVips.vips_operation_boolean_get_type 31 | LibVips.vips_operation_complex_get_type 32 | LibVips.vips_operation_complex2_get_type 33 | LibVips.vips_operation_complexget_get_type 34 | LibVips.vips_precision_get_type 35 | LibVips.vips_intent_get_type 36 | LibVips.vips_pcs_get_type 37 | LibVips.vips_extend_get_type 38 | LibVips.vips_compass_direction_get_type 39 | LibVips.vips_direction_get_type 40 | LibVips.vips_align_get_type 41 | LibVips.vips_angle_get_type 42 | LibVips.vips_angle45_get_type 43 | LibVips.vips_interesting_get_type 44 | LibVips.vips_blend_mode_get_type 45 | LibVips.vips_combine_get_type 46 | LibVips.vips_combine_mode_get_type 47 | LibVips.vips_foreign_flags_get_type 48 | LibVips.vips_fail_on_get_type 49 | LibVips.vips_saveable_get_type 50 | LibVips.vips_foreign_subsample_get_type 51 | LibVips.vips_foreign_jpeg_subsample_get_type 52 | LibVips.vips_foreign_webp_preset_get_type 53 | LibVips.vips_foreign_tiff_compression_get_type 54 | LibVips.vips_foreign_tiff_predictor_get_type 55 | LibVips.vips_foreign_tiff_resunit_get_type 56 | LibVips.vips_foreign_png_filter_get_type 57 | LibVips.vips_foreign_ppm_format_get_type 58 | LibVips.vips_foreign_dz_layout_get_type 59 | LibVips.vips_foreign_dz_depth_get_type 60 | LibVips.vips_foreign_dz_container_get_type 61 | LibVips.vips_foreign_heif_compression_get_type 62 | LibVips.vips_demand_style_get_type 63 | LibVips.vips_image_type_get_type 64 | LibVips.vips_interpretation_get_type 65 | LibVips.vips_band_format_get_type 66 | LibVips.vips_coding_get_type 67 | LibVips.vips_access_get_type 68 | LibVips.vips_operation_morphology_get_type 69 | LibVips.vips_argument_flags_get_type 70 | LibVips.vips_operation_flags_get_type 71 | LibVips.vips_region_shrink_get_type 72 | LibVips.vips_kernel_get_type 73 | LibVips.vips_size_get_type 74 | LibVips.vips_token_get_type 75 | end 76 | 77 | init 78 | -------------------------------------------------------------------------------- /scripts/mutableimage.ecr: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by running: 2 | # 3 | # scripts/gen_ext.cr 4 | # 5 | # libvips version: <%= Vips.version(0) %>.<%= Vips.version(1) %>.<%= Vips.version(2) %> 6 | # 7 | # DO NOT EDIT 8 | # 9 | # Changes to this file may cause incorrect behavior and will be lost if the code is regenerated 10 | # 11 | 12 | module Vips 13 | 14 | class MutableImage 15 | 16 | <%- nick_names.each do |nick| -%> 17 | <%= generate_method(nick,true) rescue nil %> 18 | <%- end -%> 19 | end 20 | end -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: vips 2 | version: 0.1.6 3 | 4 | authors: 5 | - Ali Naqvi 6 | 7 | description: | 8 | Crystal bindings for the libvips image processing library. 9 | 10 | crystal: '>= 0.36.0' 11 | 12 | license: MIT 13 | -------------------------------------------------------------------------------- /spec/connection_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe Vips::Connection do 4 | describe Vips::Source do 5 | it "can create a source from a descriptor" do 6 | source = Vips::Source.new_from_descriptor(0) 7 | source.should_not be(nil) 8 | end 9 | 10 | it "can create a source from a filename" do 11 | source = Vips::Source.new_from_file(simg("wagon.jpg")) 12 | source.should_not be(nil) 13 | end 14 | 15 | it "can't create a source from a bad filename" do 16 | expect_raises(Vips::VipsException, "can't create source from filename ") do 17 | Vips::Source.new_from_file(simg("banana.jpg")) 18 | end 19 | end 20 | 21 | it "can create a source from an area of memory" do 22 | str = File.read(simg("wagon.jpg")) 23 | source = Vips::Source.new_from_memory(str) 24 | source.should_not be(nil) 25 | end 26 | 27 | it "should have filenames and nicks" do 28 | source = Vips::Source.new_from_file(simg("wagon.jpg")) 29 | 30 | source.filename.should eq(simg("wagon.jpg")) 31 | source.nick.should_not be(nil) 32 | end 33 | 34 | it "can load an image from filename source" do 35 | source = Vips::Source.new_from_file(simg("wagon.jpg")) 36 | image = Vips::Image.new_from_source(source) 37 | 38 | image.width.should eq(685) 39 | image.height.should eq(478) 40 | image.bands.should eq(3) 41 | (image.avg - 109.789).should be <= 0.001 42 | end 43 | end 44 | 45 | describe Vips::Target do 46 | it "can create a target to a filename" do 47 | Vips::Target.new_to_file(timg("x.jpg")) 48 | end 49 | 50 | it "can create a target to a descriptor" do 51 | Vips::Target.new_to_descriptor(1) 52 | end 53 | 54 | it "can create a target to a memory area" do 55 | Vips::Target.new_to_memory 56 | end 57 | 58 | it "can't create a target to a bad filename" do 59 | expect_raises(Vips::VipsException) do 60 | Vips::Target.new_to_file("/banana/monkey") 61 | end 62 | end 63 | 64 | it "can save an image to a filename target" do 65 | source = Vips::Source.new_from_file(simg("wagon.jpg")) 66 | image = Vips::Image.new_from_source(source) 67 | target = Vips::Target.new_to_memory 68 | image.write_to_target(target, "%.png") 69 | memory = target.blob 70 | 71 | image = Vips::Image.new_from_buffer(memory) 72 | image.width.should eq(685) 73 | image.height.should eq(478) 74 | image.bands.should eq(3) 75 | (image.avg - 109.789).should be <= 0.001 76 | end 77 | end 78 | 79 | describe Vips::SourceCustom do 80 | it "can create a custom source" do 81 | source = Vips::SourceCustom.new 82 | end 83 | 84 | it "can load a custom source" do 85 | file = File.open(simg("wagon.jpg"), "rb") 86 | source = Vips::SourceCustom.new 87 | source.on_read { |buff| file.read(buff) } 88 | source.on_seek { |offset, whence| 89 | file.seek(offset, whence) 90 | file.pos 91 | } 92 | 93 | image = Vips::Image.new_from_source(source) 94 | 95 | image.width.should eq(685) 96 | image.height.should eq(478) 97 | image.bands.should eq(3) 98 | (image.avg - 109.789).should be <= 0.001 99 | end 100 | 101 | it "can load a source stream" do 102 | file = File.open(simg("wagon.jpg"), "rb") 103 | source = Vips::SourceStream.new_from_stream(file) 104 | 105 | image = Vips::Image.new_from_source(source) 106 | 107 | image.width.should eq(685) 108 | image.height.should eq(478) 109 | image.bands.should eq(3) 110 | (image.avg - 109.789).should be <= 0.001 111 | end 112 | 113 | it "can create a user output stream" do 114 | Vips::TargetCustom.new 115 | end 116 | 117 | it "can write an image to a user output stream" do 118 | filename = timg("x5.png") 119 | file = File.open(filename, "wb") 120 | io = IO::Memory.new 121 | target = Vips::TargetCustom.new 122 | target.on_write { |slice| io.write(slice); slice.size.to_i64 } 123 | target.on_finish do 124 | io.rewind 125 | File.open(filename, "wb") do |file| 126 | IO.copy(io, file) 127 | end 128 | io.clear 129 | end 130 | 131 | image = Vips::Image.new_from_file(simg("wagon.jpg")) 132 | image.write_to_target(target, "%.png") 133 | 134 | image = Vips::Image.new_from_file(filename) 135 | 136 | image.width.should eq(685) 137 | image.height.should eq(478) 138 | image.bands.should eq(3) 139 | (image.avg - 109.789).should be <= 0.001 140 | end 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /spec/gvalue_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe Vips::GValue do 4 | it "test bool" do 5 | gv = Vips::GValue.new 6 | gv.set_type(Vips::GValue::GBool) 7 | gv.set(true) 8 | gv.get.as_b.should be_true 9 | end 10 | 11 | it "test int" do 12 | gv = Vips::GValue.new 13 | gv.set_type(Vips::GValue::GInt) 14 | gv.set(12) 15 | gv.get.as_i32.should eq(12) 16 | end 17 | 18 | it "test Uint64" do 19 | gv = Vips::GValue.new 20 | gv.set_type(Vips::GValue::GUint64) 21 | gv.set(12_u64) 22 | gv.get.as_u64.should eq(12_u64) 23 | end 24 | 25 | it "test Double" do 26 | gv = Vips::GValue.new 27 | gv.set_type(Vips::GValue::GDouble) 28 | gv.set(12.1314) 29 | gv.get.as_f64.should eq(12.1314) 30 | end 31 | 32 | it "test Enum" do 33 | # the interpretation enum is created when the first image is made 34 | # make it ourselves in case we are run before the first image 35 | LibVips.vips_interpretation_get_type 36 | gtype = Vips.type_from_name("VipsInterpretation") 37 | 38 | gv = Vips::GValue.new 39 | gv.set_type(gtype) 40 | gv.set(Vips::Enums::Interpretation::Xyz) 41 | gv.get.as_enum(Vips::Enums::Interpretation).should eq(Vips::Enums::Interpretation::Xyz) 42 | end 43 | 44 | it "test flags" do 45 | # the OperationFlags enum is created when the first op is made 46 | # make it ourselves in case we are run before that 47 | LibVips.vips_operation_flags_get_type 48 | gtype = Vips.type_from_name("VipsOperationFlags") 49 | 50 | gv = Vips::GValue.new 51 | gv.set_type(gtype) 52 | gv.set(Vips::Enums::OperationFlags::Deprecated) 53 | 54 | gv.get.as_enum(Vips::Enums::OperationFlags).should eq(Vips::Enums::OperationFlags::Deprecated) 55 | end 56 | 57 | it "test string" do 58 | gv = Vips::GValue.new 59 | gv.set_type(Vips::GValue::GString) 60 | gv.set("hello world") 61 | gv.get.as_s.should eq("hello world") 62 | end 63 | 64 | it "test refstring" do 65 | gv = Vips::GValue.new 66 | gv.set_type(Vips::GValue::VRefStr) 67 | gv.set("hello world") 68 | gv.get.as_s.should eq("hello world") 69 | end 70 | 71 | it "test arrayint" do 72 | gv = Vips::GValue.new 73 | gv.set_type(Vips::GValue::VArrayInt) 74 | gv.set([1, 2, 3]) 75 | gv.get.as_a32.should eq([1, 2, 3]) 76 | end 77 | 78 | it "test arraydouble" do 79 | gv = Vips::GValue.new 80 | gv.set_type(Vips::GValue::VArrayDouble) 81 | gv.set([1.0, 2.0, 3.0]) 82 | gv.get.as_a64.should eq([1.0, 2.0, 3.0]) 83 | end 84 | 85 | it "test image" do 86 | image = Vips::Image.new_from_file(simg("wagon.jpg")) 87 | gv = Vips::GValue.new 88 | gv.set_type(Vips::GValue::VImageType) 89 | gv.set(image) 90 | 91 | gv.get.as_image.should eq(image) 92 | end 93 | 94 | it "test array image" do 95 | images = Vips::Image.new_from_file(simg("wagon.jpg")).bandsplit 96 | gv = Vips::GValue.new 97 | gv.set_type(Vips::GValue::VArrayImage) 98 | gv.set(images) 99 | 100 | gv.get.as_aimg.should eq(images) 101 | end 102 | 103 | it "test blob" do 104 | blob = File.read(simg("wagon.jpg")).to_slice 105 | gv = Vips::GValue.new 106 | gv.set_type(Vips::GValue::VBlob) 107 | gv.set(blob) 108 | 109 | gv.get.as_bytes.should eq(blob) 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /spec/image_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe Vips::Image do 4 | it "can save an image to a file" do 5 | filename = timg("x.v") 6 | 7 | image = Vips::Image.black(16, 16) + 128 8 | image.write_to_file(filename) 9 | 10 | File.exists?(filename).should be_true 11 | end 12 | 13 | it "can load an image from a file" do 14 | filename = timg("x.v") 15 | 16 | image = Vips::Image.black(16, 16) + 128 17 | image.write_to_file(filename) 18 | 19 | x = Vips::Image.new_from_file(filename) 20 | 21 | x.width.should eq(16) 22 | x.height.should eq(16) 23 | x.bands.should eq(1) 24 | end 25 | 26 | it "can load an image from memory" do 27 | image = Vips::Image.new_from_file(simg("wagon.jpg")) # Vips::Image.black(16,16) + 128 28 | data = image.write_to_bytes 29 | 30 | x = Vips::Image.new_from_memory(data, image.width, image.height, image.bands, image.format) 31 | 32 | image.width.should eq(685) 33 | image.height.should eq(478) 34 | image.bands.should eq(3) 35 | (image.avg - 109.789).should be <= 0.001 36 | end 37 | 38 | it "can load an image from memory by size aware address pointer" do 39 | image = Vips::Image.black(16, 16) + 128 40 | data, size = image.write_to_memory 41 | 42 | x = Vips::Image.new_from_memory_copy(data, size, image.width, image.height, image.bands, image.format) 43 | 44 | x.width.should eq(16) 45 | x.height.should eq(16) 46 | x.bands.should eq(1) 47 | x.avg.should eq(128) 48 | end 49 | 50 | if have_jpeg? 51 | it "can save an image to buffer" do 52 | image = Vips::Image.black(16, 16) + 128 53 | buffer = image.write_to_buffer("%.jpg") 54 | buffer.size.should be > 100 55 | end 56 | 57 | it "can load an image from a buffer" do 58 | image = Vips::Image.black(16, 16) + 128 59 | buffer = image.write_to_buffer("%.jpg") 60 | x = Vips::Image.new_from_buffer(buffer) 61 | x.width.should eq(16) 62 | x.height.should eq(16) 63 | end 64 | 65 | it "can load a sample jpg image file" do 66 | image = Vips::Image.new_from_file(simg("wagon.jpg")) 67 | image.width.should eq(685) 68 | image.height.should eq(478) 69 | image.bands.should eq(3) 70 | (image.avg - 109.789).should be <= 0.001 71 | end 72 | 73 | it "can load a sample jpg image buffer" do 74 | str = File.read(simg("wagon.jpg")) 75 | image = Vips::Image.new_from_buffer(str) 76 | image.width.should eq(685) 77 | image.height.should eq(478) 78 | image.bands.should eq(3) 79 | (image.avg - 109.789).should be <= 0.001 80 | end 81 | 82 | it "can extract an ICC profile from a jpg image" do 83 | image = Vips::Image.new_from_file(simg("icc.jpg")) 84 | image.width.should eq(2800) 85 | image.height.should eq(2100) 86 | image.bands.should eq(3) 87 | (image.avg - 109.189).should be <= 0.001 88 | 89 | profile = image.get("icc-profile-data").as_bytes 90 | profile.size.should eq(2360) 91 | end 92 | 93 | if Vips.at_least_libvips?(8, 10) 94 | it "can set an ICC profile on a jpg image" do 95 | image = Vips::Image.new_from_file(simg("icc.jpg")) 96 | profile = File.read(simg("lcd.icc")) 97 | image = image.copy 98 | image = image.mutate { |x| x.set("icc-profile-data", profile) } 99 | filename = timg("x.jpg") 100 | image.write_to_file(filename) 101 | 102 | image = Vips::Image.new_from_file(filename) 103 | 104 | image.width.should eq(2800) 105 | image.height.should eq(2100) 106 | image.bands.should eq(3) 107 | (image.avg - 109.189).should be <= 0.001 108 | 109 | profile = image.get("icc-profile-data").as_bytes 110 | profile.size.should eq(3048) 111 | end 112 | end 113 | 114 | it "works with arguments containing -" do 115 | image = Vips::Image.black(16, 16) + 128 116 | buffer = image.write_to_buffer("%.jpg", optimize_coding: true) 117 | buffer.size.should be > 100 118 | end 119 | 120 | it "can read exif tags" do 121 | x = Vips::Image.new_from_file(simg("huge.jpg")) 122 | orientation = x.get("exif-ifd0-Orientation").as_s 123 | 124 | orientation.size.should be > 20 125 | orientation.split[0].should eq("1") 126 | end 127 | end 128 | 129 | it "can make an image from a 2d array" do 130 | image = Vips::Image.new_from_array([[1, 2], [3, 4]]) 131 | image.width.should eq(2) 132 | image.height.should eq(2) 133 | image.bands.should eq(1) 134 | image.avg.should eq(2.5) 135 | end 136 | 137 | it "can make an image from a 1d array" do 138 | image = Vips::Image.new_from_array([1, 2]) 139 | image.width.should eq(2) 140 | image.height.should eq(1) 141 | image.bands.should eq(1) 142 | image.avg.should eq(1.5) 143 | end 144 | 145 | it "can use array consts for image args" do 146 | r = Vips::Image.black(16, 16) 147 | r = r.mutate { |i| i.draw_rect([255.0], 10, 12, 1, 1) } 148 | g = Vips::Image.black(16, 16) 149 | g = g.mutate { |i| i.draw_rect([255.0], 10, 11, 1, 1) } 150 | b = Vips::Image.black(16, 16) 151 | b = b.mutate { |i| i.draw_rect([255.0], 10, 10, 1, 1) } 152 | im = r.bandjoin(g, b) 153 | 154 | im.width.should eq(16) 155 | im.height.should eq(16) 156 | im.bands.should eq(3) 157 | 158 | im = im.conv(Vips::Image.new_from_array([ 159 | [0.11, 0.11, 0.11], 160 | [0.11, 0.11, 0.11], 161 | [0.11, 0.11, 0.11], 162 | ]), precision: Vips::Enums::Precision::Float) 163 | 164 | im.width.should eq(16) 165 | im.height.should eq(16) 166 | im.bands.should eq(3) 167 | end 168 | 169 | it "can set scale and offset on a convolution mask" do 170 | im = Vips::Image.new_from_array([1, 2], 8, 2) 171 | im.width.should eq(2) 172 | im.height.should eq(1) 173 | im.bands.should eq(1) 174 | if im.contains("scale") && im.contains("offset") 175 | im.scale.should eq(8) 176 | im.offset.should eq(2) 177 | end 178 | im.avg.should eq(1.5) 179 | end 180 | 181 | it "supports imagescale" do 182 | im = Vips::Image.new_from_array([1, 2], 8, 2) 183 | im = im.scaleimage 184 | im.width.should eq(2) 185 | im.height.should eq(1) 186 | im.max[0].should eq(255) 187 | im.min[0].should eq(0) 188 | end 189 | 190 | it "has binary arithmetic operator overloads with constants" do 191 | image = Vips::Image.black(16, 16) + 128 192 | 193 | image += 128 194 | image -= 128 195 | image *= 2 196 | image /= 2 197 | image %= 100 198 | image += 100 199 | image **= 2 200 | image <<= 1 201 | image >>= 1 202 | image |= 64 203 | image &= 32 204 | image ^= 128 205 | 206 | image.avg.should eq(128) 207 | end 208 | 209 | it "has binary arithmetic operator overloads with array constants" do 210 | image = Vips::Image.black(16, 16, bands: 3) + 128 211 | 212 | image += [128, 0, 0] 213 | image -= [128, 0, 0] 214 | image *= [2, 1, 1] 215 | image /= [2, 1, 1] 216 | image %= [100, 99, 98] 217 | image += [100, 99, 98] 218 | image **= [2, 3, 4] 219 | image **= [1.0 / 2.0, 1.0 / 3.0, 1.0 / 4.0] 220 | image <<= [1, 2, 3] 221 | image >>= [1, 2, 3] 222 | image |= [64, 128, 256] 223 | image &= [64, 128, 256] 224 | image ^= [64 + 128, 0, 256 + 128] 225 | 226 | image.avg.should eq(128) 227 | end 228 | 229 | it "has binary arithmetic operator overloads with image args" do 230 | image = Vips::Image.black(16, 16, bands: 3) + 128 231 | x = image 232 | 233 | x += image 234 | x -= image 235 | x *= image 236 | x /= image 237 | x %= image 238 | x += image 239 | x |= image 240 | x &= image 241 | x ^= image 242 | 243 | x.avg.should eq(0) 244 | end 245 | 246 | it "has relational operator overloads with constants" do 247 | image = Vips::Image.black(16, 16, bands: 3) + 128 248 | 249 | (image > 128).avg.should eq(0) 250 | (image >= 128).avg.should eq(255) 251 | (image < 128).avg.should eq(0) 252 | (image <= 128).avg.should eq(255) 253 | (image == 128).avg.should eq(255) 254 | (image != 128).avg.should eq(0) 255 | end 256 | 257 | it "has relational operator overloads with array constants" do 258 | image = Vips::Image.black(16, 16, bands: 3) + [100, 128, 130] 259 | 260 | (image > [100, 128, 130]).avg.should eq(0) 261 | (image >= [100, 128, 130]).avg.should eq(255) 262 | (image < [100, 128, 130]).avg.should eq(0) 263 | (image <= [100, 128, 130]).avg.should eq(255) 264 | (image == [100, 128, 130]).avg.should eq(255) 265 | (image != [100, 128, 130]).avg.should eq(0) 266 | end 267 | 268 | it "has relational operator overloads with image args" do 269 | image = Vips::Image.black(16, 16) + 128 270 | 271 | (image > image).avg.should eq(0) 272 | (image >= image).avg.should eq(255) 273 | (image < image).avg.should eq(0) 274 | (image <= image).avg.should eq(255) 275 | (image == image).avg.should eq(255) 276 | (image != image).avg.should eq(0) 277 | end 278 | 279 | it "has band extract with numeric arg" do 280 | image = Vips::Image.black(16, 16, bands: 3) + [100, 128, 130] 281 | x = image[1] 282 | 283 | x.width.should eq(16) 284 | x.height.should eq(16) 285 | x.bands.should eq(1) 286 | x.avg.should eq(128) 287 | end 288 | 289 | it "has band extract with range arg" do 290 | image = Vips::Image.black(16, 16, bands: 3) + [100, 128, 130] 291 | x = image[1..2] 292 | 293 | x.width.should eq(16) 294 | x.height.should eq(16) 295 | x.bands.should eq(2) 296 | x.avg.should eq(129) 297 | end 298 | 299 | it "has rounding members" do 300 | image = Vips::Image.black(16, 16) + 0.500001 301 | 302 | image.floor.avg.should eq(0) 303 | image.ceil.avg.should eq(1) 304 | image.rint.should eq(1) 305 | end 306 | 307 | it "has bandsplit and bandjoin" do 308 | image = Vips::Image.black(16, 16, bands: 3) + [100, 128, 130] 309 | 310 | split = image.bandsplit 311 | x = split[0].bandjoin(split[1..2]) 312 | 313 | x[0].avg.should eq(100) 314 | x[1].avg.should eq(128) 315 | x[2].avg.should eq(130) 316 | end 317 | 318 | it "can bandjoin constants" do 319 | image = Vips::Image.black(16, 16, bands: 3) + [100, 128, 130] 320 | 321 | x = image.bandjoin(255) 322 | 323 | x[0].avg.should eq(100) 324 | x[1].avg.should eq(128) 325 | x[2].avg.should eq(130) 326 | x[3].avg.should eq(255) 327 | x.bands.should eq(4) 328 | 329 | x = image.bandjoin(1, 2) 330 | x[0].avg.should eq(100) 331 | x[1].avg.should eq(128) 332 | x[2].avg.should eq(130) 333 | x[3].avg.should eq(1) 334 | x[4].avg.should eq(2) 335 | x.bands.should eq(5) 336 | end 337 | 338 | if Vips.at_least_libvips?(8, 6) 339 | it "can composite" do 340 | image = Vips::Image.black(16, 16, bands: 3) + [100, 128, 130] 341 | image = image.copy(interpretation: Vips::Enums::Interpretation::Srgb) 342 | base = image + 10 343 | overlay = image.bandjoin(128) 344 | comb = base.composite(overlay, Vips::Enums::BlendMode::Over) 345 | pixel = comb[0, 0] 346 | 347 | (105 - pixel[0]).should be <= 0.1 348 | (133 - pixel[1]).should be <= 0.1 349 | (135 - pixel[2]).should be <= 0.1 350 | pixel[3].should eq(255) 351 | end 352 | 353 | it "can add_alpha" do 354 | x = Vips::Image.new_from_file(simg("no_alpha.png")) 355 | x.has_alpha?.should be_false 356 | 357 | y = x.add_alpha 358 | y.has_alpha?.should be_true 359 | end 360 | end 361 | 362 | it "has minpos/maxpos" do 363 | image = Vips::Image.black(16, 16) + 128 364 | image = image.mutate { |x| x.draw_rect([255.0], 10, 12, 1, 1) } 365 | v, x, y = image.maxpos 366 | 367 | v.should eq(255) 368 | x.should eq(10) 369 | y.should eq(12) 370 | 371 | image = Vips::Image.black(16, 16) + 128 372 | image = image.mutate { |x| x.draw_rect([12.0], 10, 12, 1, 1) } 373 | v, x, y = image.minpos 374 | 375 | v.should eq(12) 376 | x.should eq(10) 377 | y.should eq(12) 378 | end 379 | 380 | it "can form complex images" do 381 | r = Vips::Image.black(16, 16) + 128 382 | i = image = Vips::Image.black(16, 16) + 12 383 | cmplx = r.complexform(i) 384 | re = cmplx.real 385 | im = cmplx.imag 386 | 387 | re.avg.should eq(128) 388 | im.avg.should eq(12) 389 | end 390 | 391 | it "can convert complex polar <-> rectangular" do 392 | r = Vips::Image.black(16, 16) + 128 393 | i = Vips::Image.black(16, 16) + 12 394 | cmplx = r.complexform(i) 395 | 396 | cmplx = cmplx.rect.polar 397 | 398 | (128 - cmplx.real.avg).should be <= 0.01 399 | (12 - cmplx.imag.avg).should be <= 0.01 400 | end 401 | 402 | it "can take complex conjugate" do 403 | r = Vips::Image.black(16, 16) + 128 404 | i = Vips::Image.black(16, 16) + 12 405 | cmplx = r.complexform(i) 406 | 407 | cmplx = cmplx.conj 408 | 409 | (128 - cmplx.real.avg).should be <= 0.001 410 | (-12 - cmplx.imag.avg).should be <= 0.001 411 | end 412 | 413 | it "has working trig functions" do 414 | image = Vips::Image.black(16, 16) + 67 415 | 416 | image = image.sin.cos.tan 417 | image = image.atan.acos.asin 418 | 419 | (67 - image.avg).should be <= 0.01 420 | end 421 | 422 | it "has working log functions" do 423 | image = Vips::Image.black(16, 16) + 67 424 | 425 | image = image.log.exp.log10.exp10 426 | 427 | (67 - image.avg).should be <= 0.01 428 | end 429 | 430 | it "can flip" do 431 | a = Vips::Image.black(16, 16) 432 | a = a.mutate { |x| x.draw_rect([255.0], 10, 12, 1, 1) } 433 | b = Vips::Image.black(16, 16) 434 | b = b.mutate { |x| x.draw_rect([255.0], 15 - 10, 12, 1, 1) } 435 | 436 | (a - b.fliphor).abs.max[0].should eq(0.0) 437 | 438 | a = Vips::Image.black(16, 16) 439 | a = a.mutate { |x| x.draw_rect([255.0], 10, 15 - 12, 1, 1) } 440 | b = Vips::Image.black(16, 16) 441 | b = b.mutate { |x| x.draw_rect([255.0], 10, 12, 1, 1) } 442 | 443 | (a - b.flipver).abs.max[0].should eq(0.0) 444 | end 445 | 446 | it "can getpoint" do 447 | a = Vips::Image.black(16, 16) 448 | a = a.mutate { |x| x.draw_rect([255.0], 10, 12, 1, 1) } 449 | b = Vips::Image.black(16, 16) 450 | b = b.mutate { |x| x.draw_rect([255.0], 10, 10, 1, 1) } 451 | im = a.bandjoin(b) 452 | 453 | im[10, 12].should eq([255, 0]) 454 | im[10, 10].should eq([0, 255]) 455 | end 456 | 457 | it "can median" do 458 | a = Vips::Image.black(16, 16) 459 | a = a.mutate { |x| x.draw_rect([255.0], 10, 12, 1, 1) } 460 | im = a.median 461 | 462 | im.max[0].should eq(0) 463 | end 464 | 465 | it "can erode" do 466 | a = Vips::Image.black(16, 16) 467 | a = a.mutate { |x| x.draw_rect([255.0], 10, 12, 1, 1) } 468 | mask = Vips::Image.new_from_array([ 469 | [128, 255, 128], 470 | [255, 255, 255], 471 | [128, 255, 128], 472 | ]) 473 | im = a.erode(mask) 474 | 475 | im.max[0].should eq(0) 476 | end 477 | 478 | it "can dilate" do 479 | a = Vips::Image.black(16, 16) 480 | a = a.mutate { |x| x.draw_rect([255.0], 10, 12, 1, 1) } 481 | mask = Vips::Image.new_from_array([ 482 | [128, 255, 128], 483 | [255, 255, 255], 484 | [128, 255, 128], 485 | ]) 486 | im = a.dilate(mask) 487 | 488 | im[10, 12].should eq([255]) 489 | im[11, 12].should eq([255]) 490 | im[12, 12].should eq([0]) 491 | end 492 | 493 | it "can rot" do 494 | a = Vips::Image.black(16, 16) 495 | a = a.mutate { |x| x.draw_rect([255.0], 10, 12, 1, 1) } 496 | 497 | im = a.rot90.rot90.rot90.rot90 498 | (a - im).abs.max[0].should eq(0) 499 | 500 | im = a.rot180.rot180 501 | (a - im).abs.max[0].should eq(0) 502 | 503 | im = a.rot270.rot270.rot270.rot270 504 | (a - im).abs.max[0].should eq(0) 505 | end 506 | 507 | it "can bandbool" do 508 | a = Vips::Image.black(16, 16) 509 | a = a.mutate { |x| x.draw_rect([255.0], 10, 12, 1, 1) } 510 | b = Vips::Image.black(16, 16) 511 | b = b.mutate { |x| x.draw_rect([255.0], 10, 10, 1, 1) } 512 | im = a.bandjoin(b) 513 | 514 | im.bandand[10, 12].should eq([0]) 515 | im.bandor[10, 12].should eq([255]) 516 | im.bandeor[10, 12].should eq([255]) 517 | end 518 | 519 | it "ifthenelse with image arguments" do 520 | image = Vips::Image.black(16, 16) 521 | image = image.mutate { |x| x.draw_rect([255.0], 10, 12, 1, 1) } 522 | black = Vips::Image.black(16, 16) 523 | white = Vips::Image.black(16, 16) + 255 524 | 525 | result = image.ifthenelse(black, white) 526 | 527 | v, x, y = result.minpos 528 | 529 | v.should eq(0) 530 | x.should eq(10) 531 | y.should eq(12) 532 | end 533 | 534 | it "ifthenelse with vector arguments" do 535 | image = Vips::Image.black(16, 16) 536 | image = image.mutate { |x| x.draw_rect([255.0], 10, 12, 1, 1) } 537 | white = Vips::Image.black(16, 16) + 255 538 | 539 | result = image.ifthenelse([255, 0, 0], white) 540 | 541 | v, x, y = result.minpos 542 | 543 | v.should eq(0) 544 | x.should eq(10) 545 | y.should eq(12) 546 | end 547 | 548 | it "has a #size method" do 549 | image = Vips::Image.black(200, 100) 550 | image.size.should eq([image.width, image.height]) 551 | end 552 | 553 | it "has a #new_from_image method" do 554 | image = Vips::Image.black(200, 100) 555 | 556 | image2 = image.new_from_image(12) 557 | image2.bands.should eq(1) 558 | image2.avg.should eq(12) 559 | 560 | image2 = image.new_from_image(1, 2, 3) 561 | image2.bands.should eq(3) 562 | image2.avg.should eq(2) 563 | end 564 | 565 | it "can make interpolate objects" do 566 | Vips::Interpolate.new_from_name("bilinear") 567 | end 568 | 569 | it "can call affine with a non-default interpolator" do 570 | image = Vips::Image.black(200, 100) 571 | inter = Vips::Interpolate.new_from_name("bilinear") 572 | result = image.affine([2.0, 0.0, 0.0, 2.0], interpolate: inter) 573 | 574 | result.width.should eq(400) 575 | result.height.should eq(200) 576 | end 577 | 578 | if Vips.at_least_libvips?(8, 5) 579 | it "can read field names" do 580 | x = Vips::Image.black(100, 100) 581 | y = x.get_fields 582 | 583 | y.size.should be > 10 584 | y[0].should eq("width") 585 | end 586 | end 587 | 588 | it "can has_alpha?" do 589 | x = Vips::Image.new_from_file(simg("alpha.png")) 590 | x.has_alpha?.should be_true 591 | end 592 | 593 | if Vips.at_least_libvips?(8, 10) 594 | it "test colourspace" do 595 | test = Vips::Image.black(100, 100) + [50, 0, 0, 42] 596 | test = test.copy(interpretation: Vips::Enums::Interpretation::Lab) 597 | colourspaces = [Vips::Enums::Interpretation::Xyz, 598 | Vips::Enums::Interpretation::Lab, 599 | Vips::Enums::Interpretation::Lch, 600 | Vips::Enums::Interpretation::Cmc, 601 | Vips::Enums::Interpretation::Labs, 602 | Vips::Enums::Interpretation::Hsv, 603 | Vips::Enums::Interpretation::Srgb, 604 | Vips::Enums::Interpretation::Yxy] 605 | 606 | im = test 607 | colourspaces.concat([Vips::Enums::Interpretation::Lab]).each do |col| 608 | im = im.colourspace(col) 609 | col.should eq(im.interpretation) 610 | (0...4).each do |i| 611 | im[i].min[0].should eq(im[i].max[0]) 612 | end 613 | pixel = im[10, 10] 614 | pixel[3].should eq(42) 615 | end 616 | 617 | # alpha won't be equal for RGB16, but it should be preserved if we go 618 | # there and back 619 | im = im.colourspace(Vips::Enums::Interpretation::Rgb16) 620 | im = im.colourspace(Vips::Enums::Interpretation::Lab) 621 | 622 | before = test[10, 10] 623 | after = im[10, 10] 624 | before.each_with_index do |v, i| 625 | (v - after[i]).should be <= 0.1 626 | end 627 | 628 | # go between every pair of color spaces 629 | colourspaces.each do |s| 630 | colourspaces.each do |e| 631 | im = test.colourspace(s) 632 | im2 = im.colourspace(e) 633 | im3 = im2.colourspace(Vips::Enums::Interpretation::Lab) 634 | 635 | before = test[10, 10] 636 | after = im3[10, 10] 637 | before.each_with_index do |v, i| 638 | (v - after[i]).should be <= 0.1 639 | end 640 | end 641 | end 642 | 643 | # test Lab->XYZ on mid-grey 644 | # checked against http://www.brucelindbloom.com 645 | im = test.colourspace(Vips::Enums::Interpretation::Xyz) 646 | after = im[10, 10] 647 | [17.5064, 18.4187, 20.0547, 42.0].each_with_index do |v, i| 648 | (v - after[i]).should be <= 0.1 649 | end 650 | 651 | # grey->colour->grey should be equal 652 | [Vips::Enums::Interpretation::Bw].each do |mono| 653 | test_grey = test.colourspace(mono) 654 | im = test_grey 655 | colourspaces.concat([mono]).each do |col| 656 | im = im.colourspace(col) 657 | col.should eq(im.interpretation) 658 | end 659 | 660 | pixel_before = test_grey[10, 10] 661 | alpha_before = pixel_before[1] 662 | pixel_after = im[10, 10] 663 | alpha_after = pixel_after[1] 664 | 665 | (alpha_after - alpha_before).abs.should be < 1 666 | 667 | # GREY16 can wind up rather different due to rounding but 8-bit we should hit exactly 668 | (pixel_after[0] - pixel_before[0]).abs.should be < (mono == Vips::Enums::Interpretation::Grey16 ? 30 : 1) 669 | end 670 | end 671 | end 672 | 673 | it "test hist_cum" do 674 | im = Vips::Image.identity 675 | sum = im.avg * 256 676 | cum = im.hist_cum 677 | 678 | p = cum[255, 0] 679 | sum.should eq(p[0]) 680 | end 681 | 682 | it "test hist_equal" do 683 | im = Vips::Image.new_from_file(simg("wagon.jpg")) 684 | im2 = im.hist_equal 685 | 686 | im.width.should eq(im2.width) 687 | im.height.should eq(im2.height) 688 | 689 | im.avg.should be < im2.avg 690 | im.deviate.should be < im2.deviate 691 | end 692 | 693 | it "test hist_ismonotonic" do 694 | im = Vips::Image.identity 695 | im.hist_ismonotonic.should be_true 696 | end 697 | 698 | it "test hist_local" do 699 | im = Vips::Image.new_from_file(simg("wagon.jpg")) 700 | im2 = im.hist_local(10, 10) 701 | 702 | im.width.should eq(im2.width) 703 | im.height.should eq(im2.height) 704 | 705 | im.avg.should be < im2.avg 706 | im.deviate.should be < im2.deviate 707 | 708 | if Vips.at_least_libvips?(8, 5) 709 | im3 = im.hist_local(10, 10, max_slope: 3) 710 | im.width.should eq(im3.width) 711 | im.height.should eq(im3.height) 712 | 713 | im3.deviate.should be < im2.deviate 714 | end 715 | end 716 | 717 | it "test hist_match" do 718 | im = Vips::Image.identity 719 | im2 = Vips::Image.identity 720 | 721 | matched = im.hist_match(im2) 722 | 723 | (im - matched).abs.max[0].should eq(0.0) 724 | end 725 | 726 | it "test hist_norm" do 727 | im = Vips::Image.identity 728 | im2 = im.hist_norm 729 | (im - im2).abs.max[0].should eq(0.0) 730 | end 731 | 732 | it "test hist_plot" do 733 | im = Vips::Image.identity 734 | im2 = im.hist_plot 735 | 736 | im2.width.should eq(256) 737 | im2.height.should eq(256) 738 | im2.format.should eq(Vips::Enums::BandFormat::Uchar) 739 | im2.bands.should eq(1) 740 | end 741 | 742 | it "test hist_map" do 743 | im = Vips::Image.identity 744 | im2 = im.maplut(im) 745 | (im - im2).abs.max[0].should eq(0.0) 746 | end 747 | 748 | it "test percent" do 749 | im = Vips::Image.new_from_file(simg("wagon.jpg"))[1] 750 | pc = im.percent(90) 751 | 752 | msk = im <= pc 753 | nset = (msk.avg * msk.width * msk.height) / 255.0 754 | pcset = 100 * nset / (msk.width * msk.height) 755 | 756 | (pcset - 90).abs.should be < 1 757 | end 758 | end 759 | -------------------------------------------------------------------------------- /spec/mutableimage_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe Vips::MutableImage do 4 | it "can draw circle" do 5 | im = Vips::Image.black(100, 100) 6 | im = im.mutate { |x| x.draw_circle([100.0], 50, 50, 25) } 7 | pixel = im[25, 50] 8 | pixel.size.should eq(1) 9 | pixel[0].should eq(100) 10 | pixel = im[26, 50] 11 | pixel.size.should eq(1) 12 | pixel[0].should eq(0) 13 | 14 | im = Vips::Image.black(100, 100) 15 | im = im.mutate { |x| x.draw_circle([100.0], 50, 50, 25, fill: true) } 16 | pixel = im[25, 50] 17 | pixel.size.should eq(1) 18 | pixel[0].should eq(100) 19 | pixel = im[26, 50] 20 | pixel.size.should eq(1) 21 | pixel[0].should eq(100) 22 | pixel = im[24, 50] 23 | pixel[0].should eq(0) 24 | end 25 | 26 | it "can draw flood" do 27 | im = Vips::Image.black(100, 100) 28 | im = im.mutate { |x| 29 | x.draw_circle([100.0], 50, 50, 25) 30 | x.draw_flood([100.0], 50, 50) 31 | } 32 | 33 | im2 = Vips::Image.black(100, 100) 34 | im2 = im2.mutate { |x| x.draw_circle([100.0], 50, 50, 25, fill: true) } 35 | 36 | diff = (im - im2).abs.max 37 | diff[0].should eq(0) 38 | end 39 | 40 | it "can draw image" do 41 | im = Vips::Image.black(100, 100) 42 | im = im.mutate { |x| x.draw_circle([100.0], 25, 25, 25, fill: true) } 43 | 44 | im2 = Vips::Image.black(100, 100) 45 | im2 = im2.mutate { |x| x.draw_image(im, 25, 25) } 46 | 47 | im3 = Vips::Image.black(100, 100) 48 | im3 = im3.mutate { |x| x.draw_circle([100.0], 50, 50, 25, fill: true) } 49 | 50 | diff = (im2 - im3).abs.max 51 | diff[0].should eq(0) 52 | end 53 | 54 | it "can draw line" do 55 | im = Vips::Image.black(100, 100) 56 | im = im.mutate { |x| x.draw_line([100.0], 0, 0, 100, 0) } 57 | pixel = im[0, 0] 58 | pixel.size.should eq(1) 59 | pixel[0].should eq(100) 60 | 61 | pixel = im[0, 1] 62 | pixel.size.should eq(1) 63 | pixel[0].should eq(0) 64 | end 65 | 66 | it "can draw mask" do 67 | mask = Vips::Image.black(51, 51) 68 | mask = mask.mutate { |x| x.draw_circle([128.0], 25, 25, 25, fill: true) } 69 | 70 | im = Vips::Image.black(100, 100) 71 | im = im.mutate { |x| x.draw_mask([200.0], mask, 25, 25) } 72 | 73 | im2 = Vips::Image.black(100, 100) 74 | im2 = im2.mutate { |x| x.draw_circle([100.0], 50, 50, 25, fill: true) } 75 | 76 | diff = (im - im2).abs.max 77 | diff[0].should eq(0) 78 | end 79 | 80 | it "can draw rect" do 81 | im = Vips::Image.black(100, 100) 82 | im = im.mutate { |x| x.draw_rect([100.0], 25, 25, 50, 50, fill: true) } 83 | 84 | im2 = Vips::Image.black(100, 100) 85 | im2 = im2.mutate { |x| 86 | (25...75).each { |y| x.draw_line([100.0], 25, y, 74, y) } 87 | } 88 | 89 | diff = (im - im2).abs.max 90 | diff[0].should eq(0) 91 | end 92 | 93 | it "can draw smudge" do 94 | im = Vips::Image.black(100, 100) 95 | im = im.mutate { |x| x.draw_circle([100.0], 50, 50, 25) } 96 | 97 | im2 = im.extract_area(10, 10, 50, 50) 98 | 99 | im3 = im.mutate do |x| 100 | x.draw_smudge(10, 10, 50, 50) 101 | x.draw_image(im2, 10, 10) 102 | end 103 | 104 | diff = (im3 - im).abs.max 105 | diff[0].should eq(0) 106 | end 107 | 108 | it "test countlines" do 109 | im = Vips::Image.black(100, 100) 110 | im = im.mutate { |x| x.draw_line([255.0], 0, 50, 100, 50) } 111 | nlines = im.countlines(Vips::Enums::Direction::Horizontal) 112 | nlines.to_i.should eq(1) 113 | end 114 | 115 | if Vips.at_least_libvips?(8, 10) 116 | it "test labelregions" do 117 | im = Vips::Image.black(100, 100) 118 | im = im.mutate { |x| x.draw_circle([100.0], 50, 50, 25, fill: true) } 119 | 120 | mask, segments = im.labelregions 121 | segments.should eq(3) 122 | mask.max[0].should eq(2) 123 | end 124 | 125 | it "test rank" do 126 | im = Vips::Image.black(100, 100) 127 | im = im.mutate { |x| x.draw_circle([100.0], 50, 50, 25, fill: true) } 128 | im2 = im.rank(3, 3, 8) 129 | im.width.should eq(im2.width) 130 | im.height.should eq(im2.height) 131 | im.bands.should eq(im2.bands) 132 | im2.avg.should be > im.avg 133 | end 134 | 135 | it "can set metadata in mutate" do 136 | image = Vips::Image.black(16, 16) 137 | image = image.mutate { |x| x.set(Vips::GValue::GInt, "banana", 12) } 138 | 139 | image.get("banana").as_i32.should eq(12) 140 | end 141 | 142 | it "can remove metadata in mutate" do 143 | image = Vips::Image.black(16, 16) 144 | image = image.mutate { |x| x.set(Vips::GValue::GInt, "banana", 12) } 145 | 146 | image = image.mutate { |x| x.remove("banana") } 147 | image.get_typeof("banana").should eq(0) 148 | end 149 | end 150 | 151 | it "cannot call non-destructive operations in mutate" do 152 | image = Vips::Image.black(16, 16) 153 | expect_raises(Vips::VipsException, "operation must be mutable") do 154 | image.mutate { |x| x.invert } 155 | end 156 | end 157 | end 158 | -------------------------------------------------------------------------------- /spec/region_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe Vips::Region do 4 | it "can create a region on an image" do 5 | image = Vips::Image.black(100, 100) 6 | Vips::Region.new(image) 7 | end 8 | 9 | if Vips.at_least_libvips?(8, 8) 10 | it "can fetch pixels from a region" do 11 | image = Vips::Image.black(100, 100) 12 | region = Vips::Region.new(image) 13 | pixel_data = region.fetch(10, 20, 30, 40) 14 | 15 | pixel_data.should_not be_nil 16 | pixel_data.size.should eq(30 * 40) 17 | end 18 | 19 | it "can make regions with width and height" do 20 | image = Vips::Image.black(100, 100) 21 | region = Vips::Region.new(image) 22 | pixel_data = region.fetch(10, 20, 30, 40) 23 | 24 | pixel_data.should_not be_nil 25 | region.width.should eq(30) 26 | region.height.should eq(40) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/samples/alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naqvis/crystal-vips/0f4d3914985865a020168b0f48ece07416eeb459/spec/samples/alpha.png -------------------------------------------------------------------------------- /spec/samples/balloon.v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naqvis/crystal-vips/0f4d3914985865a020168b0f48ece07416eeb459/spec/samples/balloon.v -------------------------------------------------------------------------------- /spec/samples/ghost.ppm: -------------------------------------------------------------------------------- 1 | P3 2 | # Created by Paint Shop Pro 7 3 | 49 52 4 | 255 5 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 6 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 7 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 8 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 9 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 10 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 11 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 12 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 13 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 14 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 15 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 16 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 17 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 18 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 19 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 20 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 21 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 22 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 23 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 24 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 25 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 26 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 27 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 28 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 29 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 30 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 31 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 32 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 33 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 34 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 35 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 36 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 37 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 38 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 39 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 40 | 255 255 255 255 255 255 255 255 255 248 248 248 204 255 255 204 236 255 41 | 204 236 255 204 255 255 204 236 255 241 241 241 255 255 255 255 255 255 42 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 43 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 44 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 45 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 46 | 255 255 255 255 255 255 255 255 255 241 241 241 241 241 241 255 251 240 47 | 248 248 248 248 248 248 248 248 248 241 241 241 255 251 240 248 248 248 48 | 255 251 240 248 248 248 204 236 255 102 204 255 51 204 255 0 204 255 0 204 255 49 | 0 204 255 0 204 204 0 204 255 51 204 255 102 204 204 204 236 255 248 248 248 50 | 255 251 240 255 251 240 248 248 248 248 248 248 248 248 248 248 248 248 51 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 251 240 52 | 248 248 248 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 53 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 54 | 255 255 255 255 255 255 248 248 248 241 241 241 255 251 240 248 248 248 55 | 255 255 255 248 248 248 255 251 240 248 248 248 255 255 255 241 241 241 56 | 51 204 255 0 204 204 0 204 255 0 204 255 0 204 255 0 204 204 0 204 204 57 | 51 153 204 0 204 255 0 204 255 0 204 255 0 204 204 51 204 204 153 255 255 58 | 248 248 248 255 251 240 241 241 241 248 248 248 248 248 248 255 251 240 59 | 255 251 240 255 255 255 248 248 248 248 248 248 255 251 240 204 255 255 60 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 61 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 62 | 255 255 255 255 255 255 248 248 248 255 255 255 248 248 248 255 255 255 63 | 248 248 248 255 251 240 255 255 255 166 202 240 0 204 255 0 153 204 0 102 204 64 | 0 153 204 0 204 255 0 153 153 51 102 153 51 51 102 0 102 102 0 204 204 65 | 0 204 204 0 204 255 0 204 255 0 204 255 0 204 204 102 204 255 255 255 255 66 | 255 255 204 255 251 240 255 251 240 255 251 240 255 251 240 255 251 240 67 | 248 248 248 248 248 248 248 248 248 255 251 240 255 255 255 255 255 255 68 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 69 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 70 | 255 255 255 255 255 255 255 255 255 255 251 240 255 251 240 255 255 255 71 | 153 255 255 51 204 255 51 204 255 0 153 204 0 102 153 51 51 102 160 160 164 72 | 255 204 255 241 241 241 204 255 255 234 234 234 160 160 164 51 51 102 51 102 153 73 | 0 204 255 0 204 255 0 204 255 51 153 255 204 236 255 241 241 241 255 255 255 74 | 255 255 255 241 241 241 204 236 255 255 251 240 255 255 204 255 251 240 75 | 255 255 255 241 241 241 255 255 255 255 255 255 255 255 255 255 255 255 76 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 77 | 255 255 255 255 255 255 255 255 255 255 251 240 255 255 255 255 255 255 78 | 255 255 255 255 251 240 248 248 248 204 255 255 51 204 204 0 204 255 51 153 153 79 | 0 128 128 51 153 153 204 236 255 255 251 240 255 255 204 255 255 204 255 255 204 80 | 255 251 240 255 251 240 241 241 241 102 102 153 51 153 204 0 204 204 0 204 255 81 | 51 204 255 51 204 255 51 255 255 51 255 255 51 204 255 51 204 255 0 204 255 82 | 102 204 255 241 241 241 248 248 248 248 248 248 248 248 248 255 255 255 83 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 84 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 85 | 255 251 240 255 255 255 255 255 255 255 255 255 255 255 255 241 241 241 86 | 102 204 204 0 204 204 0 153 204 0 51 51 102 153 153 241 241 241 241 241 241 87 | 255 251 240 255 255 204 255 255 204 255 251 240 255 251 240 204 255 204 88 | 255 255 204 255 251 240 119 119 119 0 128 128 0 153 204 0 204 255 0 204 255 89 | 0 204 255 0 204 255 0 128 128 0 102 102 0 153 204 0 204 255 102 204 255 90 | 234 234 234 241 241 241 255 255 204 255 255 255 255 255 255 255 255 255 91 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 92 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 93 | 255 255 255 255 251 240 255 255 255 204 255 255 51 204 204 0 204 255 51 204 255 94 | 102 102 102 248 248 248 241 241 241 255 251 240 255 251 240 255 251 240 95 | 255 251 240 255 251 240 255 251 240 231 231 214 255 255 204 255 255 204 96 | 255 255 255 57 57 57 51 102 153 0 204 204 0 204 255 0 204 255 0 204 255 97 | 51 102 102 66 66 66 51 204 204 0 204 255 0 204 255 51 204 204 153 255 255 98 | 241 241 241 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 99 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 100 | 255 255 255 255 255 255 248 248 248 255 255 255 248 248 248 255 251 240 101 | 255 255 255 153 204 255 0 204 255 0 204 255 0 51 102 204 204 204 255 255 255 102 | 255 251 240 241 241 241 255 251 240 255 251 240 255 251 240 255 251 240 103 | 255 251 240 241 241 241 255 251 240 255 255 204 255 255 204 234 234 234 104 | 0 51 102 51 204 204 0 204 255 51 204 255 51 153 204 51 51 0 102 51 51 66 66 66 105 | 51 153 153 51 204 204 0 204 255 0 153 255 102 204 204 248 248 248 255 255 255 106 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 107 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 108 | 255 255 255 248 248 248 255 251 240 255 255 255 102 204 255 0 204 255 0 204 255 109 | 51 51 102 248 248 248 255 251 240 255 251 240 255 251 240 255 255 204 255 255 204 110 | 255 255 204 255 251 240 255 251 240 248 248 248 102 102 102 239 214 198 111 | 255 251 240 255 255 255 102 153 204 0 204 204 0 204 204 51 102 153 153 102 51 112 | 255 102 51 255 102 51 255 102 51 204 102 51 153 102 51 51 153 153 0 204 255 113 | 0 204 204 153 204 204 248 248 248 255 255 255 255 255 255 255 255 255 255 255 255 114 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 115 | 255 255 255 248 248 248 255 255 255 255 251 240 255 255 255 248 248 248 116 | 51 204 255 0 204 255 0 204 255 51 153 153 241 241 241 255 255 204 255 255 204 117 | 255 251 240 255 251 240 255 255 204 255 255 204 102 102 102 204 204 204 118 | 231 231 214 102 102 153 57 57 57 255 251 240 255 251 240 204 236 255 0 128 128 119 | 51 153 102 255 124 128 204 102 51 204 51 51 255 102 51 204 51 0 204 102 51 120 | 255 102 51 204 102 51 0 204 204 51 204 255 0 204 255 204 236 255 255 251 240 121 | 255 251 240 248 248 248 255 255 255 255 255 255 248 248 248 255 255 255 122 | 255 255 255 255 255 255 255 255 255 255 255 255 248 248 248 255 255 255 123 | 255 251 240 255 255 255 248 248 248 51 204 204 0 204 255 0 204 255 0 102 153 124 | 248 248 248 255 255 204 255 255 204 255 251 240 255 251 240 255 251 240 125 | 231 231 214 150 150 150 22 22 22 234 234 234 51 51 102 51 51 51 255 251 240 126 | 255 251 240 241 241 241 57 57 57 255 204 153 255 102 51 102 51 51 204 102 102 127 | 204 51 51 153 102 51 153 51 51 255 102 51 255 102 51 85 85 85 0 204 255 128 | 0 204 255 153 204 255 255 251 240 255 251 240 255 255 255 255 255 255 255 255 255 129 | 255 255 255 248 248 248 255 255 255 255 255 255 255 255 255 255 255 255 130 | 255 255 255 248 248 248 255 255 255 255 255 255 248 248 248 51 204 204 131 | 0 204 255 0 204 204 0 102 153 248 248 248 255 255 204 255 255 204 255 251 240 132 | 255 251 240 255 251 240 215 215 215 95 95 95 51 0 51 248 248 248 128 128 128 133 | 150 150 150 255 251 240 255 251 240 255 204 204 204 51 51 255 102 51 204 102 0 134 | 204 153 0 204 102 0 153 51 0 255 153 51 255 102 51 255 102 51 255 102 51 135 | 204 102 51 0 204 204 0 204 255 153 204 255 255 251 240 255 251 240 255 255 255 136 | 255 255 255 255 255 255 255 251 240 248 248 248 255 255 255 255 255 255 137 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 248 248 248 138 | 248 248 248 51 204 204 0 204 255 0 204 255 0 102 153 227 227 227 255 255 204 139 | 255 251 240 255 251 240 255 251 240 255 251 240 255 251 240 178 178 178 140 | 160 160 164 255 255 204 160 160 164 255 255 255 255 251 240 255 255 204 141 | 255 204 204 204 102 51 255 102 51 153 102 51 153 102 51 153 51 51 153 102 51 142 | 204 51 51 204 102 0 153 102 0 255 102 51 255 102 0 0 153 204 0 204 255 143 | 153 204 255 248 248 248 255 255 255 255 255 255 248 248 248 255 255 255 144 | 255 251 240 248 248 248 255 255 255 255 255 255 255 255 255 255 255 255 145 | 248 248 248 255 255 255 248 248 248 248 248 248 248 248 248 102 204 255 146 | 0 204 255 0 204 255 0 102 153 192 192 192 255 251 240 255 251 240 255 251 240 147 | 255 251 240 255 251 240 255 255 204 255 255 204 255 255 255 227 227 227 148 | 153 102 204 102 102 153 215 215 215 255 251 240 255 251 240 204 153 102 149 | 204 153 51 153 102 51 204 204 51 204 204 51 204 153 51 153 153 0 255 255 51 150 | 153 102 0 255 102 51 255 51 51 0 153 255 51 204 204 204 255 255 255 255 255 151 | 255 255 255 255 255 255 248 248 248 248 248 248 255 251 240 255 255 255 152 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 153 | 248 248 248 255 251 240 255 255 255 153 204 255 0 204 204 0 204 255 0 153 204 154 | 102 153 153 241 241 241 255 251 240 255 251 240 255 251 240 255 255 204 155 | 255 255 204 255 255 204 248 248 248 102 102 153 153 102 255 153 102 255 156 | 51 102 153 255 251 240 255 255 204 239 214 198 153 102 51 255 102 51 204 204 51 157 | 153 102 0 204 153 51 204 204 51 153 153 0 204 102 51 255 102 102 102 51 51 158 | 0 204 255 51 204 204 248 248 248 248 248 248 248 248 248 255 255 255 248 248 248 159 | 248 248 248 255 251 240 255 255 255 255 255 255 255 255 255 255 255 255 160 | 255 255 255 255 251 240 255 255 255 248 248 248 255 251 240 255 255 255 161 | 204 255 255 0 204 204 0 204 255 0 204 255 102 153 153 204 236 255 255 251 240 162 | 255 255 204 255 255 204 255 251 240 255 255 204 255 251 240 150 150 150 163 | 102 102 204 153 102 255 153 102 255 102 102 153 215 215 215 255 251 240 164 | 255 255 204 102 51 51 204 102 102 204 102 51 204 51 51 153 51 51 153 51 0 165 | 204 153 51 204 102 102 102 51 102 51 204 204 0 204 204 153 255 255 255 255 255 166 | 255 251 240 255 251 240 255 255 255 248 248 248 241 241 241 255 251 240 167 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 251 240 168 | 255 255 255 248 248 248 255 251 240 248 248 248 204 255 255 51 204 204 169 | 0 204 255 0 204 255 51 102 102 166 202 240 255 251 240 255 255 204 255 251 240 170 | 248 248 248 255 251 240 215 215 215 0 0 102 0 51 102 0 0 204 102 102 255 171 | 102 102 153 192 192 192 255 251 240 255 251 240 134 134 134 51 153 204 172 | 102 51 0 153 51 102 204 102 102 204 102 51 102 51 51 85 85 85 51 204 204 173 | 0 204 255 51 204 204 248 248 248 241 241 241 255 251 240 255 251 240 255 255 255 174 | 248 248 248 241 241 241 255 251 240 248 248 248 255 255 255 255 255 255 175 | 255 255 255 255 255 255 255 251 240 255 255 255 248 248 248 255 251 240 176 | 248 248 248 204 255 255 51 204 204 0 204 255 0 204 255 0 51 102 166 202 240 177 | 255 251 240 255 255 204 255 251 240 255 255 255 255 255 255 178 178 178 178 | 0 0 128 0 51 102 0 51 153 102 102 204 153 51 153 204 204 204 255 255 255 179 | 255 251 240 153 204 204 0 102 204 192 220 192 234 234 234 221 221 221 204 236 255 180 | 51 153 153 0 153 204 0 204 255 51 204 255 248 248 248 255 255 255 248 248 248 181 | 255 251 240 255 251 240 255 255 255 248 248 248 241 241 241 255 251 240 182 | 248 248 248 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 183 | 248 248 248 255 255 255 255 251 240 248 248 248 204 255 255 51 204 204 184 | 0 204 255 0 204 255 0 102 153 203 203 203 255 255 204 255 255 204 241 241 241 185 | 231 231 214 255 255 204 160 160 164 0 51 153 0 51 153 0 51 153 102 102 204 186 | 102 51 204 255 204 255 255 255 204 255 255 204 255 251 240 102 102 153 187 | 241 241 241 204 236 255 204 236 255 241 241 241 51 51 102 0 102 153 0 204 204 188 | 204 236 255 248 248 248 255 255 255 255 255 255 255 255 255 255 255 255 189 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 190 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 191 | 255 251 240 248 248 248 204 255 255 51 204 204 0 204 255 0 204 255 0 102 102 192 | 227 227 227 255 255 204 255 251 240 241 241 241 255 251 240 255 255 204 193 | 178 178 178 0 0 128 0 51 153 0 51 153 102 102 204 153 102 204 255 255 255 194 | 255 251 240 192 220 192 134 134 134 241 241 241 234 234 234 204 236 255 195 | 204 236 255 241 241 241 51 51 102 51 153 204 102 204 255 255 255 255 255 251 240 196 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 197 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 198 | 255 255 255 248 248 248 255 255 255 255 255 255 255 251 240 248 248 248 199 | 153 255 255 51 204 255 0 204 255 0 204 255 51 102 153 241 241 241 255 255 204 200 | 255 251 240 150 150 150 255 251 240 255 251 240 203 203 203 0 51 102 0 51 153 201 | 51 51 153 153 102 204 153 102 204 204 204 204 160 160 164 192 192 192 241 241 241 202 | 234 234 234 204 236 255 204 236 255 234 234 234 234 234 234 51 102 153 203 | 51 204 255 204 236 255 255 251 240 255 251 240 255 255 255 255 255 255 204 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 205 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 248 248 248 206 | 255 255 255 255 255 255 255 251 240 248 248 248 102 204 255 0 204 255 0 204 255 207 | 0 204 255 102 153 102 255 251 240 255 255 204 255 251 240 215 215 215 77 77 77 208 | 203 203 203 248 248 248 51 102 102 51 51 204 102 102 204 102 51 153 153 153 204 209 | 227 227 227 241 241 241 204 236 255 204 236 255 234 234 234 204 236 255 210 | 234 234 234 241 241 241 153 153 204 0 153 153 51 204 255 248 248 248 255 251 240 211 | 248 248 248 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 212 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 213 | 255 255 255 255 255 255 248 248 248 255 255 255 255 255 255 255 255 255 214 | 241 241 241 51 204 204 0 204 255 0 204 255 0 204 204 134 134 134 255 251 240 215 | 255 251 240 255 251 240 255 255 255 192 192 192 95 95 95 160 160 164 153 153 204 216 | 51 51 153 102 102 153 227 227 227 241 241 241 234 234 234 204 236 255 204 236 255 217 | 204 236 255 234 234 234 204 236 255 227 227 227 248 248 248 102 153 153 218 | 0 204 204 0 204 255 204 236 255 255 251 240 248 248 248 255 255 255 255 255 255 219 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 220 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 248 248 248 221 | 255 255 255 255 255 255 248 248 248 204 255 255 51 204 204 0 204 255 0 204 255 222 | 0 102 102 192 220 192 255 251 240 255 251 240 255 251 240 255 251 240 255 251 240 223 | 173 169 144 221 221 221 241 241 241 241 241 241 248 248 248 227 227 227 224 | 234 234 234 227 227 227 204 236 255 204 236 255 234 234 234 234 234 234 225 | 234 234 234 204 236 255 241 241 241 102 153 153 51 204 204 0 204 255 102 255 255 226 | 255 255 255 248 248 248 255 255 255 255 255 255 255 255 255 255 255 255 227 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 228 | 255 255 255 255 255 255 255 255 255 255 251 240 255 255 255 255 255 255 229 | 248 248 248 102 204 255 0 204 204 0 204 255 51 153 204 0 51 102 255 255 255 230 | 231 231 214 255 251 240 255 251 240 255 255 204 255 251 240 255 251 240 231 | 178 178 178 234 234 234 234 234 234 234 234 234 234 234 234 227 227 227 232 | 241 241 241 234 234 234 234 234 234 234 234 234 234 234 234 234 234 234 233 | 204 236 255 204 236 255 166 202 240 51 153 153 0 204 255 0 204 204 241 241 241 234 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 235 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 236 | 255 255 255 255 255 255 255 251 240 255 255 255 255 255 255 204 255 255 237 | 51 204 204 0 204 255 0 204 255 0 153 153 153 153 153 255 255 204 231 231 214 238 | 255 251 240 255 251 240 255 255 204 255 255 204 255 251 240 192 192 192 239 | 178 178 178 234 234 234 241 241 241 241 241 241 204 236 255 234 234 234 240 | 234 234 234 234 234 234 204 236 255 204 236 255 227 227 227 204 236 255 241 | 204 236 255 241 241 241 51 102 102 0 204 204 0 204 255 102 204 204 248 248 248 242 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 243 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 244 | 255 255 255 255 251 240 255 255 255 248 248 248 102 204 255 0 204 204 0 204 255 245 | 0 204 255 51 102 153 234 234 234 239 214 198 255 251 240 255 251 240 255 251 240 246 | 255 255 204 255 255 204 255 251 240 255 251 240 178 178 178 134 134 134 247 | 150 150 150 192 192 192 241 241 241 204 236 255 234 234 234 234 234 234 248 | 204 236 255 204 236 255 204 255 204 234 234 234 204 236 255 248 248 248 249 | 153 153 153 0 102 153 0 204 255 0 204 204 166 202 240 255 255 255 248 248 248 250 | 255 255 255 255 255 255 255 255 255 248 248 248 255 255 255 255 255 255 251 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 248 248 248 252 | 255 255 255 153 255 255 0 204 204 0 204 255 0 204 255 0 204 204 102 153 204 253 | 255 255 204 239 214 198 255 255 255 192 192 192 178 178 178 255 255 255 254 | 255 251 240 255 255 204 255 251 240 255 251 240 255 251 240 204 204 204 255 | 192 192 192 241 241 241 241 241 241 234 234 234 234 234 234 234 234 234 256 | 234 234 234 255 204 255 204 236 255 204 236 255 234 234 234 255 204 255 257 | 57 57 57 0 153 153 0 204 255 0 204 255 204 236 255 241 241 241 241 241 241 258 | 241 241 241 248 248 248 241 241 241 248 248 248 248 248 248 255 251 240 259 | 255 255 255 255 255 255 255 255 255 255 255 255 241 241 241 255 255 255 260 | 51 204 255 0 204 255 0 204 255 0 204 255 51 153 204 255 251 240 160 160 164 261 | 255 251 240 255 251 240 150 150 150 150 150 150 204 204 204 255 251 240 262 | 255 251 240 255 255 204 255 251 240 231 231 214 128 128 128 248 248 248 263 | 241 241 241 204 236 255 204 236 255 234 234 234 234 234 234 204 236 255 264 | 204 236 255 204 236 255 204 236 255 204 236 255 241 241 241 153 204 204 265 | 0 51 102 0 153 204 0 204 255 102 204 204 241 241 241 255 255 255 255 251 240 266 | 255 251 240 255 255 255 255 255 255 248 248 248 255 251 240 255 255 255 267 | 255 255 255 255 255 255 255 255 255 255 251 240 153 204 255 0 204 255 0 204 255 268 | 51 204 255 51 153 153 227 227 227 231 231 214 178 178 178 255 251 240 204 204 204 269 | 203 203 203 241 241 241 134 134 134 255 251 240 255 251 240 255 255 204 270 | 255 255 204 255 251 240 203 203 203 204 204 204 204 236 255 204 236 255 271 | 234 234 234 234 234 234 234 234 234 204 236 255 204 236 255 204 236 255 272 | 204 236 255 234 234 234 227 227 227 241 241 241 102 102 153 0 153 204 0 204 255 273 | 0 204 204 204 236 255 255 255 255 248 248 248 241 241 241 255 255 255 255 255 255 274 | 248 248 248 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 275 | 241 241 241 0 204 255 0 204 255 0 153 255 0 102 153 192 192 192 255 255 204 276 | 150 150 150 241 241 241 255 251 240 192 192 192 215 215 215 241 241 241 277 | 150 150 150 255 251 240 255 251 240 255 255 204 255 251 240 255 251 240 278 | 234 234 234 160 160 164 241 241 241 204 236 255 204 236 255 234 234 234 279 | 204 236 255 204 236 255 234 234 234 234 234 234 234 234 234 234 234 234 280 | 234 234 234 234 234 234 241 241 241 51 153 153 0 204 204 0 204 204 102 153 204 281 | 255 255 255 248 248 248 248 248 248 255 255 255 255 255 255 255 255 255 282 | 241 241 241 255 255 255 255 255 255 255 255 255 255 255 255 102 204 255 283 | 0 204 204 0 204 255 0 102 153 150 150 150 255 255 255 178 178 178 204 204 153 284 | 255 255 255 255 251 240 160 160 164 234 234 234 234 234 234 160 160 164 285 | 241 241 241 255 251 240 231 231 214 255 251 240 178 178 178 178 178 178 286 | 178 178 178 241 241 241 204 236 255 204 236 255 234 234 234 204 236 255 287 | 204 236 255 255 204 255 204 236 255 204 236 255 204 236 255 234 234 234 288 | 234 234 234 241 241 241 204 236 255 51 51 102 0 153 204 0 153 204 204 255 255 289 | 255 255 255 255 251 240 248 248 248 255 255 255 248 248 248 248 248 248 290 | 255 255 255 255 255 255 255 255 255 255 255 255 0 204 255 0 204 255 51 153 204 291 | 119 119 119 255 255 255 234 234 234 128 128 128 255 255 204 255 255 255 292 | 255 251 240 153 153 153 234 234 234 234 234 234 192 192 192 221 221 221 293 | 255 251 240 221 221 221 255 255 255 150 150 150 215 215 215 234 234 234 294 | 204 236 255 204 236 255 234 234 234 234 234 234 204 236 255 204 236 255 295 | 234 234 234 204 236 255 204 236 255 234 234 234 234 234 234 241 241 241 296 | 241 241 241 153 204 204 102 153 153 0 153 204 0 204 204 102 255 255 248 248 248 297 | 255 251 240 248 248 248 248 248 248 255 255 255 248 248 248 255 255 255 298 | 255 255 255 255 255 255 255 255 255 0 204 255 0 204 204 102 102 153 255 255 255 299 | 204 236 255 153 204 204 178 178 178 255 255 255 255 251 240 234 234 234 300 | 153 153 153 241 241 241 234 234 234 215 215 215 192 192 192 215 215 215 301 | 134 134 134 215 215 215 153 153 153 204 236 255 204 236 255 204 236 255 302 | 204 236 255 234 234 234 234 234 234 234 234 234 234 234 234 204 236 255 303 | 234 234 234 234 234 234 255 255 255 192 192 192 51 102 153 0 102 153 0 153 153 304 | 0 153 204 51 204 255 0 204 204 153 255 255 255 255 255 255 255 255 255 255 255 305 | 248 248 248 255 255 255 248 248 248 255 255 255 255 255 255 255 255 255 306 | 255 255 255 204 236 255 0 204 204 51 153 153 248 248 248 248 248 248 134 134 134 307 | 255 251 240 241 241 241 255 255 204 203 203 203 192 192 192 241 241 241 308 | 204 236 255 241 241 241 192 192 192 178 178 178 241 241 241 192 192 192 309 | 241 241 241 234 234 234 241 241 241 204 236 255 204 236 255 234 234 234 310 | 234 234 234 234 234 234 234 234 234 204 236 255 241 241 241 241 241 241 311 | 51 102 102 0 153 153 51 204 255 51 204 255 0 204 255 0 204 255 0 204 204 312 | 102 204 204 241 241 241 255 255 255 255 255 255 255 255 255 255 255 255 313 | 255 255 255 248 248 248 255 255 255 255 255 255 255 255 255 255 255 255 314 | 248 248 248 102 255 204 0 204 204 51 153 153 66 66 66 204 204 204 255 251 240 315 | 255 255 204 255 251 240 178 178 178 234 234 234 204 236 255 204 236 255 316 | 204 236 255 241 241 241 234 234 234 234 234 234 234 234 234 204 236 255 317 | 204 236 255 204 236 255 204 236 255 204 236 255 204 236 255 234 234 234 318 | 234 234 234 234 234 234 204 255 255 204 204 204 28 28 28 0 102 102 0 153 153 319 | 0 204 204 0 204 255 0 204 255 0 204 255 153 255 255 248 248 248 248 248 248 320 | 248 248 248 255 251 240 255 255 255 248 248 248 255 255 255 241 241 241 321 | 255 255 255 255 255 255 255 255 255 255 255 255 255 251 240 248 248 248 322 | 0 204 255 0 204 255 0 128 128 204 255 255 241 241 241 239 214 198 178 178 178 323 | 204 153 204 241 241 241 204 236 255 204 236 255 204 236 255 204 236 255 324 | 204 236 255 234 234 234 241 241 241 204 236 255 204 236 255 234 234 234 325 | 234 234 234 227 227 227 241 241 241 204 236 255 204 236 255 241 241 241 326 | 134 134 134 51 51 0 51 51 0 102 51 0 51 51 0 66 66 66 51 102 102 51 204 204 327 | 0 204 255 102 204 255 248 248 248 255 251 240 255 255 204 255 251 240 255 255 255 328 | 248 248 248 255 255 255 248 248 248 255 255 255 255 255 255 255 255 255 329 | 255 255 255 248 248 248 255 255 255 51 204 255 0 204 255 0 204 255 51 153 153 330 | 0 51 51 8 8 8 4 4 4 153 204 153 221 221 221 241 241 241 241 241 241 234 234 234 331 | 234 234 234 204 236 255 204 236 255 204 236 255 204 236 255 234 234 234 332 | 227 227 227 227 227 227 227 227 227 241 241 241 248 248 248 192 192 192 333 | 34 34 34 102 51 0 153 102 0 102 102 0 102 102 0 102 51 0 153 51 0 153 51 51 334 | 0 51 51 51 204 153 51 204 204 102 204 255 241 241 241 255 255 255 248 248 248 335 | 255 255 255 255 255 255 255 255 255 248 248 248 255 255 255 255 255 255 336 | 255 255 255 255 255 255 255 255 255 248 248 248 0 204 255 0 204 255 51 153 153 337 | 51 102 51 102 51 51 102 51 0 51 51 0 17 17 17 22 22 22 34 34 34 95 95 95 338 | 160 160 164 234 234 234 248 248 248 241 241 241 204 236 255 241 241 241 339 | 241 241 241 234 234 234 248 248 248 248 248 248 178 178 178 51 51 102 51 51 51 340 | 85 85 85 51 102 102 51 102 102 0 128 128 0 128 128 0 128 128 51 102 153 341 | 51 153 204 0 153 204 0 204 255 0 204 255 51 153 204 204 255 255 241 241 241 342 | 248 248 248 248 248 248 255 255 255 255 255 255 255 251 240 255 255 255 343 | 255 255 255 255 255 255 255 255 255 248 248 248 204 236 255 51 204 204 344 | 51 102 102 102 51 51 153 51 0 153 51 0 153 51 0 153 51 0 102 51 51 102 51 51 345 | 77 77 77 51 51 51 51 51 51 57 57 57 66 66 66 134 134 134 192 192 192 204 204 204 346 | 204 204 204 192 192 192 51 153 153 0 102 102 0 153 153 0 204 255 0 204 255 347 | 0 204 255 0 204 255 0 204 255 0 204 255 0 204 204 0 204 255 0 204 255 0 204 255 348 | 0 204 255 51 204 255 102 204 204 153 204 255 241 241 241 255 255 255 248 248 248 349 | 255 255 255 248 248 248 248 248 248 241 241 241 255 255 255 255 255 255 350 | 255 255 255 255 255 255 248 248 248 153 255 204 0 204 204 0 153 153 66 66 66 351 | 66 66 66 51 102 102 0 128 128 0 128 128 0 153 153 0 153 204 0 204 204 0 204 255 352 | 0 204 255 0 204 255 0 204 204 0 153 204 0 153 153 0 153 204 0 153 204 0 153 204 353 | 51 153 204 51 204 204 102 204 204 102 204 204 166 202 240 204 236 255 241 241 241 354 | 241 241 241 241 241 241 241 241 241 204 255 255 204 255 255 241 241 241 355 | 248 248 248 248 248 248 248 248 248 255 255 255 255 255 255 255 255 255 356 | 255 255 255 248 248 248 248 248 248 248 248 248 255 255 255 255 255 255 357 | 255 255 255 255 255 255 255 255 255 248 248 248 234 234 234 102 204 255 358 | 51 204 255 51 204 204 51 204 204 51 204 255 51 204 255 51 204 255 102 204 255 359 | 102 204 255 102 204 255 102 204 255 102 204 255 102 204 255 51 204 255 360 | 51 204 255 102 204 255 153 204 255 153 204 255 204 236 255 204 255 255 361 | 204 255 255 248 248 248 248 248 248 255 255 255 255 255 255 255 255 255 362 | 248 248 248 248 248 248 248 248 248 248 248 248 255 255 255 248 248 248 363 | 248 248 248 255 255 255 255 251 240 255 251 240 255 251 240 255 255 255 364 | 255 255 255 248 248 248 255 255 255 248 248 248 248 248 248 255 255 255 365 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 366 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 367 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 368 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 369 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 370 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 371 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 372 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 373 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 374 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 375 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 376 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 377 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 378 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 379 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 380 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 381 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 382 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 383 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 384 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 385 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 386 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 387 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 388 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 389 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 390 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 391 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 392 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 393 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 394 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 395 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 396 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 397 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 398 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 399 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 400 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 401 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 402 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 403 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 404 | 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 405 | 255 255 255 255 255 255 255 255 255 255 255 255 406 | -------------------------------------------------------------------------------- /spec/samples/huge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naqvis/crystal-vips/0f4d3914985865a020168b0f48ece07416eeb459/spec/samples/huge.jpg -------------------------------------------------------------------------------- /spec/samples/icc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naqvis/crystal-vips/0f4d3914985865a020168b0f48ece07416eeb459/spec/samples/icc.jpg -------------------------------------------------------------------------------- /spec/samples/lcd.icc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naqvis/crystal-vips/0f4d3914985865a020168b0f48ece07416eeb459/spec/samples/lcd.icc -------------------------------------------------------------------------------- /spec/samples/lion.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /spec/samples/no_alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naqvis/crystal-vips/0f4d3914985865a020168b0f48ece07416eeb459/spec/samples/no_alpha.png -------------------------------------------------------------------------------- /spec/samples/sample.csv: -------------------------------------------------------------------------------- 1 | 0,0,0,255,0,0,0 2 | 0,0,0,255,0,0,0 3 | 0,0,0,255,0,0,0 4 | 0,0,0,255,0,0,0 5 | 0,255,0,255,0,0,0 6 | 0,255,0,255,0,0,0 7 | 0,0,255,255,0,0,0 8 | -------------------------------------------------------------------------------- /spec/samples/sample.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naqvis/crystal-vips/0f4d3914985865a020168b0f48ece07416eeb459/spec/samples/sample.exr -------------------------------------------------------------------------------- /spec/samples/wagon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naqvis/crystal-vips/0f4d3914985865a020168b0f48ece07416eeb459/spec/samples/wagon.jpg -------------------------------------------------------------------------------- /spec/samples/wagon.v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naqvis/crystal-vips/0f4d3914985865a020168b0f48ece07416eeb459/spec/samples/wagon.v -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "file" 3 | 4 | # Set default concurrency so we can check against it later. Must be set 5 | # before Vips.init sets concurrency to the default. 6 | DEFAULT_VIPS_CONCURRENCY = 5 7 | ENV["VIPS_CONCURRENCY"] = DEFAULT_VIPS_CONCURRENCY.to_s 8 | 9 | # Disable stderr output since we purposefully trigger warn-able behavior. 10 | ENV["VIPS_WARNING"] = "1" 11 | 12 | require "../src/vips" 13 | 14 | def simg(name) 15 | File.join(__DIR__, "samples", name) 16 | end 17 | 18 | def timg(name) 19 | File.tempname(name) 20 | end 21 | 22 | def have(name) 23 | Vips.typefind("VipsOperation", name) != 0 24 | end 25 | 26 | def have_jpeg? 27 | have("jpegload") 28 | end 29 | -------------------------------------------------------------------------------- /spec/vips_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe Vips do 4 | it "can get default concurrency" do 5 | Vips.default_concurrency.should eq(DEFAULT_VIPS_CONCURRENCY) 6 | end 7 | 8 | it "can get concurrency" do 9 | Vips.concurrency.should eq(Vips.default_concurrency) 10 | end 11 | 12 | it "can set concurrency" do 13 | Vips.concurrency = 12 14 | Vips.concurrency.should eq(12) 15 | end 16 | 17 | it "clips concurrency to 1024" do 18 | Vips.concurrency = 1025 19 | Vips.concurrency.should eq(1024) 20 | end 21 | 22 | it "can set concurrency to 0 to reset to default" do 23 | Vips.concurrency = Random.new.rand(100) 24 | 25 | Vips.concurrency = 0 26 | Vips.concurrency.should eq(Vips.default_concurrency) 27 | end 28 | 29 | it "sets SIMD" do 30 | default = Vips.vector? 31 | Vips.vector = true 32 | Vips.vector?.should be_true 33 | 34 | Vips.vector = false 35 | Vips.vector?.should be_false 36 | 37 | Vips.vector = default 38 | end 39 | 40 | it "can enable leak testing" do 41 | Vips.leak = true 42 | Vips.leak = false 43 | end 44 | 45 | it "can get a set of filename suffixes" do 46 | suffs = Vips.get_suffixes 47 | (suffs.size > 10).should be_true unless suffs.empty? 48 | end 49 | 50 | describe Vips::Cache do 51 | it "can get and set the operation cache size" do 52 | default = Vips::Cache.max 53 | 54 | Vips::Cache.max = 0 55 | Vips::Cache.max.should eq(0) 56 | 57 | Vips::Cache.max = default 58 | Vips::Cache.max.should eq(default) 59 | end 60 | 61 | it "can get and set the operation cache memory limit" do 62 | default = Vips::Cache.max_mem 63 | 64 | Vips::Cache.max_mem = 0 65 | Vips::Cache.max_mem.should eq(0) 66 | 67 | Vips::Cache.max_mem = default 68 | Vips::Cache.max_mem.should eq(default) 69 | end 70 | 71 | it "can set the operation cache file limit" do 72 | default = Vips::Cache.max_files 73 | 74 | Vips::Cache.max_files = 0 75 | Vips::Cache.max_files.should eq(0) 76 | 77 | Vips::Cache.max_files = default 78 | Vips::Cache.max_files.should eq(default) 79 | end 80 | end 81 | 82 | describe Vips::Stats do 83 | it "can get allocated bytes" do 84 | Vips::Stats.mem.should be >= 0 85 | end 86 | 87 | it "can get allocated bytes high-water mark" do 88 | Vips::Stats.mem_highwater.should be >= 0 89 | end 90 | 91 | it "can get allocation count" do 92 | Vips::Stats.allocations.should be >= 0 93 | end 94 | 95 | it "can get open file count" do 96 | Vips::Stats.open_files.should be >= 0 97 | end 98 | end 99 | 100 | describe Vips::Operation do 101 | it "can make a black image" do 102 | image = Vips::Operation.call("black", 200, 200).as(Vips::Type).as_image 103 | image.width.should eq(200) 104 | image.height.should eq(200) 105 | image.bands.should eq(1) 106 | end 107 | 108 | it "can take an optional argument" do 109 | image = Vips::Operation.call("black", Vips::Optional.new(bands: 12), 200, 200).as(Vips::Type).as_image 110 | image.width.should eq(200) 111 | image.height.should eq(200) 112 | image.bands.should eq(12) 113 | end 114 | 115 | it "ignore optional arguments with nil values" do 116 | image = Vips::Operation.call("black", Vips::Optional.new(bands: nil), 200, 200).as(Vips::Type).as_image 117 | image.width.should eq(200) 118 | image.height.should eq(200) 119 | image.bands.should eq(1) 120 | end 121 | 122 | it "can handle enum arguments" do 123 | black = Vips::Operation.call("black", 200, 200).as(Vips::Type).as_image 124 | image = Vips::Operation.call("embed", Vips::Optional.new(extend: Vips::Enums::Extend::Mirror), 125 | black, 10, 10, 500, 500).as(Vips::Type).as_image 126 | 127 | image.width.should eq(500) 128 | image.height.should eq(500) 129 | image.bands.should eq(1) 130 | end 131 | 132 | it "can return optional output args" do 133 | point = Vips::Operation.call("black", 1, 1).as(Vips::Type).as_image 134 | test = Vips::Operation.call("embed", Vips::Optional.new(extend: Vips::Enums::Extend::White), 135 | point, 20, 10, 100, 100).as(Vips::Type).as_image 136 | 137 | value = Vips::Operation.call("min", Vips::Optional.new(x: true, y: true), test).as(Array(Vips::Type)) 138 | opts = value[1].as_h 139 | value[0].as_i32.should eq(0) 140 | opts["x"].as_i32.should eq(20) 141 | opts["y"].as_i32.should eq(10) 142 | end 143 | 144 | it "can call draw operations" do 145 | black = Vips::Operation.call("black", 100, 100).as(Vips::Type).as_image 146 | max_black = Vips::Operation.call("max", nil, black).as(Vips::Type).as_f64 147 | Vips::Operation.call("draw_rect", nil, black, 255, 10, 10, 1, 1) 148 | max_test = Vips::Operation.call("max", nil, black).as(Vips::Type).as_f64 149 | 150 | max_black.should eq(0.0) 151 | max_test.should eq(255) 152 | end 153 | 154 | it "can throw errors for failed operations" do 155 | black = Vips::Operation.call("black", 100, 1).as(Vips::Type).as_image 156 | expect_raises(Vips::VipsException, "bad extract area") do 157 | black.crop(10, 10, 1, 1) 158 | end 159 | end 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /src/vips.cr: -------------------------------------------------------------------------------- 1 | require "./vips/lib" 2 | require "./vips/vips" 3 | require "./vips/gobject" 4 | require "./vips/vipsobject" 5 | require "./vips/gvalue" 6 | require "./vips/operation" 7 | require "./vips/introspect" 8 | require "./vips/interpolate" 9 | require "./vips/cache" 10 | require "./vips/enums" 11 | require "./vips/image" 12 | require "./vips/mutableimage" 13 | require "./vips/region" 14 | require "./vips/vipsblob" 15 | require "./vips/connection" 16 | require "./vips/source" 17 | require "./vips/target" 18 | require "./vips/stats" 19 | require "./vips/ext/**" 20 | 21 | # Provides Crystal language interface to the [libvips](https://github.com/libvips/libvips) image processing library. 22 | # Programs that use libvips don't manipulate images directly, instead they create pipelines of image processing 23 | # operations starting from a source image. When the pipe is connected to a destination, the whole pipeline executes 24 | # at once and in parallel, streaming the image from source to destination in a set of small fragments. 25 | module Vips 26 | VERSION = {{ `shards version "#{__DIR__}"`.chomp.stringify }} 27 | end 28 | -------------------------------------------------------------------------------- /src/vips/cache.cr: -------------------------------------------------------------------------------- 1 | module Vips::Cache 2 | # Gets the maximum number of operations libvips keep in cache 3 | def self.max : Int32 4 | LibVips.vips_cache_get_max 5 | end 6 | 7 | # Sets the maximum number of operations libvips keep in cache 8 | def self.max=(value : Int32) 9 | LibVips.vips_cache_set_max(value) 10 | end 11 | 12 | # Gets the maximum amount of tracked memory allowed. 13 | def self.max_mem 14 | LibVips.vips_cache_get_max_mem 15 | end 16 | 17 | # Sets the maximum amount of tracked memory allowed. 18 | def self.max_mem=(value : LibC::SizeT) 19 | LibVips.vips_cache_set_max_mem(value) 20 | end 21 | 22 | # Gets the maximum amount of tracked files allowed. 23 | def self.max_files 24 | LibVips.vips_cache_get_max_files 25 | end 26 | 27 | # Sets the maximum amount of tracked files allowed. 28 | def self.max_files=(value : Int32) 29 | LibVips.vips_cache_set_max_files(value) 30 | end 31 | 32 | # Gets the current number of operations in cache. 33 | def self.size 34 | LibVips.vips_cache_get_size 35 | end 36 | 37 | # Enable or disable libvips cache tracing. 38 | def self.trace=(value : Bool) 39 | LibVips.vips_cache_set_trace(value) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /src/vips/connection.cr: -------------------------------------------------------------------------------- 1 | module Vips 2 | abstract class Connection < VipsObject 3 | protected def initialize(@chandle : LibVips::VipsConnection*) 4 | super(@chandle.as(LibVips::VipsObject*)) 5 | end 6 | 7 | # Get the filename associated with a connection or nil if there is no associated file 8 | def filename : String? 9 | ptr = LibVips.vips_connection_filename(@chandle) 10 | ptr.null? ? nil : String.new(ptr) 11 | end 12 | 13 | # Make a human-readable name for a connection suitable for error messages 14 | def nick : String? 15 | ptr = LibVips.vips_connection_nick(@chandle) 16 | ptr.null? ? nil : String.new(ptr) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/vips/enums.cr: -------------------------------------------------------------------------------- 1 | module Vips::Enums 2 | enum Access 3 | # Requests can come in any order 4 | Random = 0 5 | 6 | # Means requests will be top-to-bottom, but with some 7 | # amount of buffering behind the read point for small non-local 8 | # accesses. 9 | Sequential = 1 10 | 11 | # Top-to-bottom without a buffer. 12 | SequentialUnbuffered = 2 13 | end 14 | 15 | # Various types of alignment. 16 | enum Align 17 | # Align on the low coordinate edge 18 | Low = 0 19 | 20 | # Align on the centre. 21 | Centre = 1 22 | 23 | # Align on the high coordinate edge 24 | High = 2 25 | end 26 | 27 | enum Angle 28 | D0 = 0 29 | D90 = 1 30 | D180 = 2 31 | D270 = 3 32 | end 33 | 34 | enum Angle45 35 | D0 = 0 36 | D45 = 1 37 | D90 = 2 38 | D135 = 3 39 | D180 = 4 40 | D225 = 5 41 | D270 = 6 42 | D315 = 7 43 | end 44 | 45 | enum BandFormat 46 | Notset = -1 47 | Uchar = 0 48 | Char = 1 49 | Ushort = 2 50 | Short = 3 51 | Uint = 4 52 | Int = 5 53 | Float = 6 54 | Complex = 7 55 | Double = 8 56 | Dpcomplex = 9 57 | end 58 | 59 | enum BlendMode 60 | Clear = 0 61 | Source = 1 62 | Over = 2 63 | In = 3 64 | Out = 4 65 | Atop = 5 66 | Dest = 6 67 | DestOver = 7 68 | DestIn = 8 69 | DestOut = 9 70 | DestAtop = 10 71 | Xor = 11 72 | Add = 12 73 | Saturate = 13 74 | Multiply = 14 75 | Screen = 15 76 | Overlay = 16 77 | Darken = 17 78 | Lighten = 18 79 | ColourDodge = 19 80 | ColourBurn = 20 81 | HardLight = 21 82 | SoftLight = 22 83 | Difference = 23 84 | Exclusion = 24 85 | end 86 | 87 | enum Coding 88 | Error = -1 89 | None = 0 90 | Labq = 2 91 | Rad = 6 92 | end 93 | 94 | enum Combine 95 | Max = 0 96 | Sum = 1 97 | Min = 2 98 | end 99 | 100 | enum CombineMode 101 | Set = 0 102 | Add = 1 103 | end 104 | 105 | enum CompassDirection 106 | Centre = 0 107 | North = 1 108 | East = 2 109 | South = 3 110 | West = 4 111 | NorthEast = 5 112 | SouthEast = 6 113 | SouthWest = 7 114 | NorthWest = 8 115 | end 116 | 117 | enum DemandStyle 118 | Error = -1 119 | Smalltile = 0 120 | Fatstrip = 1 121 | Thinstrip = 2 122 | end 123 | 124 | enum Direction 125 | Horizontal = 0 126 | Vertical = 1 127 | end 128 | 129 | enum Extend 130 | Black = 0 131 | Copy = 1 132 | Repeat = 2 133 | Mirror = 3 134 | White = 4 135 | Background = 5 136 | end 137 | 138 | # How sensitive loaders are to errors, from never stop (very insensitive), to 139 | # stop on the smallest warning (very sensitive). 140 | # 141 | # Each one implies the ones before it, so `Error` implies `Truncated` 142 | enum FailOn 143 | # Never stop 144 | None = 0 145 | 146 | # Stop on image truncated, nothing else 147 | Truncated = 1 148 | 149 | # Stop on serious error or truncation 150 | Error = 2 151 | 152 | # Stop on anything, even warnings 153 | Warning = 3 154 | end 155 | 156 | enum ForeignDzContainer 157 | Fs = 0 158 | Zip = 1 159 | Szi = 2 160 | end 161 | 162 | enum ForeignDzDepth 163 | Onepixel = 0 164 | Onetile = 1 165 | One = 2 166 | end 167 | 168 | enum ForeignDzLayout 169 | Dz = 0 170 | Zoomify = 1 171 | Google = 2 172 | Iiif = 3 173 | Iiif3 = 4 174 | end 175 | 176 | enum ForeignHeifCompression 177 | Hevc = 1 178 | Avc = 2 179 | Jpeg = 3 180 | Av1 = 4 181 | end 182 | 183 | enum ForeignJpegSubsample 184 | Auto = 0 185 | On = 1 186 | Off = 2 187 | end 188 | 189 | enum ForeignPpmFormat 190 | Pbm = 0 191 | Pgm = 1 192 | Ppm = 2 193 | Pfm = 3 194 | end 195 | 196 | enum ForeignSubsample 197 | Auto = 0 198 | On = 1 199 | Off = 2 200 | end 201 | 202 | enum ForeignTiffCompression 203 | None = 0 204 | Jpeg = 1 205 | Deflate = 2 206 | Packbits = 3 207 | Ccittfax4 = 4 208 | Lzw = 5 209 | Webp = 6 210 | Zstd = 7 211 | Jp2k = 8 212 | end 213 | 214 | enum ForeignTiffPredictor 215 | None = 1 216 | Horizontal = 2 217 | Float = 3 218 | end 219 | 220 | enum ForeignTiffResunit 221 | Cm = 0 222 | Inch = 1 223 | end 224 | 225 | enum ForeignWebpPreset 226 | Default = 0 227 | Picture = 1 228 | Photo = 2 229 | Drawing = 3 230 | Icon = 4 231 | Text = 5 232 | end 233 | 234 | enum ImageType 235 | Error = -1 236 | None = 0 237 | Setbuf = 1 238 | SetbufForeign = 2 239 | Openin = 3 240 | Mmapin = 4 241 | Mmapinrw = 5 242 | Openout = 6 243 | end 244 | 245 | enum Intent 246 | Perceptual = 0 247 | Relative = 1 248 | Saturation = 2 249 | Absolute = 3 250 | end 251 | 252 | enum Interesting 253 | None = 0 254 | Centre = 1 255 | Entropy = 2 256 | Attention = 3 257 | Low = 4 258 | High = 5 259 | All = 6 260 | end 261 | 262 | enum Interpretation 263 | Error = -1 264 | Multiband = 0 265 | Bw = 1 266 | Histogram = 10 267 | Xyz = 12 268 | Lab = 13 269 | Cmyk = 15 270 | Labq = 16 271 | Rgb = 17 272 | Cmc = 18 273 | Lch = 19 274 | Labs = 21 275 | Srgb = 22 276 | Yxy = 23 277 | Fourier = 24 278 | Rgb16 = 25 279 | Grey16 = 26 280 | Matrix = 27 281 | Scrgb = 28 282 | Hsv = 29 283 | end 284 | 285 | enum Kernel 286 | Nearest = 0 287 | Linear = 1 288 | Cubic = 2 289 | Mitchell = 3 290 | Lanczos2 = 4 291 | Lanczos3 = 5 292 | end 293 | 294 | enum OperationBoolean 295 | And = 0 296 | Or = 1 297 | Eor = 2 298 | Lshift = 3 299 | Rshift = 4 300 | end 301 | 302 | enum OperationComplex 303 | Polar = 0 304 | Rect = 1 305 | Conj = 2 306 | end 307 | 308 | enum OperationComplex2 309 | CrossPhase = 0 310 | end 311 | 312 | enum OperationComplexget 313 | Real = 0 314 | Imag = 1 315 | end 316 | 317 | enum OperationMath 318 | Sin = 0 319 | Cos = 1 320 | Tan = 2 321 | Asin = 3 322 | Acos = 4 323 | Atan = 5 324 | Log = 6 325 | Log10 = 7 326 | Exp = 8 327 | Exp10 = 9 328 | Sinh = 10 329 | Cosh = 11 330 | Tanh = 12 331 | Asinh = 13 332 | Acosh = 14 333 | Atanh = 15 334 | end 335 | 336 | enum OperationMath2 337 | Pow = 0 338 | Wop = 1 339 | Atan2 = 2 340 | end 341 | 342 | enum OperationMorphology 343 | Erode = 0 344 | Dilate = 1 345 | end 346 | 347 | enum OperationRelational 348 | Equal = 0 349 | Noteq = 1 350 | Less = 2 351 | Lesseq = 3 352 | More = 4 353 | Moreeq = 5 354 | end 355 | 356 | enum OperationRound 357 | Rint = 0 358 | Ceil = 1 359 | Floor = 2 360 | end 361 | 362 | enum PCS 363 | Lab = 0 364 | Xyz = 1 365 | end 366 | 367 | enum Precision 368 | Integer = 0 369 | Float = 1 370 | Approximate = 2 371 | end 372 | 373 | enum RegionShrink 374 | Mean = 0 375 | Median = 1 376 | Mode = 2 377 | Max = 3 378 | Min = 4 379 | Nearest = 5 380 | end 381 | 382 | enum Saveable 383 | Mono = 0 384 | Rgb = 1 385 | Rgba = 2 386 | RgbaOnly = 3 387 | RgbCmyk = 4 388 | Any = 5 389 | end 390 | 391 | enum Size 392 | Both = 0 393 | Up = 1 394 | Down = 2 395 | Force = 3 396 | end 397 | 398 | enum Token 399 | Left = 1 400 | Right = 2 401 | String = 3 402 | Equals = 4 403 | end 404 | 405 | @[Flags] 406 | enum OperationFlags 407 | Sequential = 1 408 | SequentialUnbuffered = 2 409 | Nocache = 4 410 | Deprecated = 8 411 | end 412 | 413 | @[Flags] 414 | enum ForeignFlags 415 | Partial = 1 416 | Bigendian = 2 417 | Sequential = 4 418 | end 419 | 420 | # Signals that can be used on an `Image`. See `GObject#signal_connect` 421 | enum Signal 422 | # Evaluation is starting 423 | # The preeval signal is emitted once before computation of `Image` starts. 424 | # It's a good place to set up evaluation feedback. 425 | PreEval = 0 426 | 427 | # The eval signal is emitted once per work unit (typically a 128 x 128 are of pixels) 428 | # during image computation 429 | # 430 | # You can use this signal to update user-interfaces with progress feedback. 431 | # Beware of updating too frequently: you will usually need some throttling mechanism 432 | Eval = 1 433 | 434 | # Ealuation is ending 435 | # The posteval signal is emitted once at the end of the computation of `Image`. 436 | # It's a good place to shut down evaluation feedback. 437 | PostEval = 2 438 | end 439 | 440 | # :nodoc: 441 | protected def self.add_failon(optionals : Optional, failon : Enums::FailOn?) 442 | return if failon.nil? 443 | if Vips.at_least_libvips?(8, 12) 444 | optionals["fail_on"] = failon.value 445 | else 446 | # The deprecated "fail" param was at the highest sensitivity (>= warning), 447 | # but for compat it would be more correct to set this to true only when 448 | # a non-permissive enum is given (> none). 449 | optionals["fail"] = failon > FailOn::None 450 | end 451 | end 452 | end 453 | -------------------------------------------------------------------------------- /src/vips/ext/mutableimage.cr: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by running: 2 | # 3 | # scripts/gen_ext.cr 4 | # 5 | # libvips version: 8.14.2 6 | # 7 | # DO NOT EDIT 8 | # 9 | # Changes to this file may cause incorrect behavior and will be lost if the code is regenerated 10 | # 11 | 12 | module Vips 13 | class MutableImage 14 | # Draw a circle on an image 15 | # 16 | # ``` 17 | # # image.mutate { |x| x.draw_circle(ink, cx, cy, radius, {fill: Bool} } 18 | # ``` 19 | # 20 | # 21 | # Input Parameters 22 | # 23 | # **Required** 24 | # 25 | # *ink* : Array(Float64) - Color for pixels 26 | # 27 | # *cx* : Int32 - Centre of draw_circle 28 | # 29 | # *cy* : Int32 - Centre of draw_circle 30 | # 31 | # *radius* : Int32 - Radius in pixels 32 | # 33 | # _Optionals_ 34 | # 35 | # *fill* : Bool - Draw a solid object 36 | # 37 | def draw_circle(ink : Array(Float64), cx : Int32, cy : Int32, radius : Int32, **kwargs) : Nil 38 | options = Optional.new(**kwargs) 39 | self.call("draw_circle", options, ink, cx, cy, radius) 40 | end 41 | 42 | # Flood-fill an area 43 | # 44 | # ``` 45 | # # image.mutate { |x| x.draw_flood(ink, x, y, {test: Image, equal: Bool} } 46 | # ``` 47 | # 48 | # 49 | # Input Parameters 50 | # 51 | # **Required** 52 | # 53 | # *ink* : Array(Float64) - Color for pixels 54 | # 55 | # *x* : Int32 - DrawFlood start point 56 | # 57 | # *y* : Int32 - DrawFlood start point 58 | # 59 | # _Optionals_ 60 | # 61 | # *test* : Image - Test pixels in this image 62 | # 63 | # *equal* : Bool - DrawFlood while equal to edge 64 | # 65 | def draw_flood(ink : Array(Float64), x : Int32, y : Int32, **kwargs) : Nil 66 | options = Optional.new(**kwargs) 67 | self.call("draw_flood", options, ink, x, y) 68 | end 69 | 70 | # Paint an image into another image 71 | # 72 | # ``` 73 | # # image.mutate { |x| x.draw_image(sub, x, y, {mode: Enums::CombineMode} } 74 | # ``` 75 | # 76 | # 77 | # Input Parameters 78 | # 79 | # **Required** 80 | # 81 | # *sub* : Image - Sub-image to insert into main image 82 | # 83 | # *x* : Int32 - Draw image here 84 | # 85 | # *y* : Int32 - Draw image here 86 | # 87 | # _Optionals_ 88 | # 89 | # *mode* : Enums::CombineMode - Combining mode 90 | # 91 | def draw_image(sub : Image, x : Int32, y : Int32, **kwargs) : Nil 92 | options = Optional.new(**kwargs) 93 | self.call("draw_image", options, sub, x, y) 94 | end 95 | 96 | # Draw a line on an image 97 | # 98 | # ``` 99 | # # image.mutate { |x| x.draw_line(ink, x1, y1, x2, y2 } 100 | # ``` 101 | # 102 | # 103 | # Input Parameters 104 | # 105 | # **Required** 106 | # 107 | # *ink* : Array(Float64) - Color for pixels 108 | # 109 | # *x1* : Int32 - Start of draw_line 110 | # 111 | # *y1* : Int32 - Start of draw_line 112 | # 113 | # *x2* : Int32 - End of draw_line 114 | # 115 | # *y2* : Int32 - End of draw_line 116 | # 117 | def draw_line(ink : Array(Float64), x1 : Int32, y1 : Int32, x2 : Int32, y2 : Int32) : Nil 118 | self.call("draw_line", ink, x1, y1, x2, y2) 119 | end 120 | 121 | # Draw a mask on an image 122 | # 123 | # ``` 124 | # # image.mutate { |x| x.draw_mask(ink, mask, x, y } 125 | # ``` 126 | # 127 | # 128 | # Input Parameters 129 | # 130 | # **Required** 131 | # 132 | # *ink* : Array(Float64) - Color for pixels 133 | # 134 | # *mask* : Image - Mask of pixels to draw 135 | # 136 | # *x* : Int32 - Draw mask here 137 | # 138 | # *y* : Int32 - Draw mask here 139 | # 140 | def draw_mask(ink : Array(Float64), mask : Image, x : Int32, y : Int32) : Nil 141 | self.call("draw_mask", ink, mask, x, y) 142 | end 143 | 144 | # Paint a rectangle on an image 145 | # 146 | # ``` 147 | # # image.mutate { |x| x.draw_rect(ink, left, top, width, height, {fill: Bool} } 148 | # ``` 149 | # 150 | # 151 | # Input Parameters 152 | # 153 | # **Required** 154 | # 155 | # *ink* : Array(Float64) - Color for pixels 156 | # 157 | # *left* : Int32 - Rect to fill 158 | # 159 | # *top* : Int32 - Rect to fill 160 | # 161 | # *width* : Int32 - Rect to fill 162 | # 163 | # *height* : Int32 - Rect to fill 164 | # 165 | # _Optionals_ 166 | # 167 | # *fill* : Bool - Draw a solid object 168 | # 169 | def draw_rect(ink : Array(Float64), left : Int32, top : Int32, width : Int32, height : Int32, **kwargs) : Nil 170 | options = Optional.new(**kwargs) 171 | self.call("draw_rect", options, ink, left, top, width, height) 172 | end 173 | 174 | # Blur a rectangle on an image 175 | # 176 | # ``` 177 | # # image.mutate { |x| x.draw_smudge(left, top, width, height } 178 | # ``` 179 | # 180 | # 181 | # Input Parameters 182 | # 183 | # **Required** 184 | # 185 | # *left* : Int32 - Rect to fill 186 | # 187 | # *top* : Int32 - Rect to fill 188 | # 189 | # *width* : Int32 - Rect to fill 190 | # 191 | # *height* : Int32 - Rect to fill 192 | # 193 | def draw_smudge(left : Int32, top : Int32, width : Int32, height : Int32) : Nil 194 | self.call("draw_smudge", left, top, width, height) 195 | end 196 | end 197 | end 198 | -------------------------------------------------------------------------------- /src/vips/gobject.cr: -------------------------------------------------------------------------------- 1 | module Vips 2 | class GObject 3 | protected def initialize(@handle : LibVips::GObject*) 4 | end 5 | 6 | # Connects a `callback` to a signal on this object. 7 | # The callback will be triggered every time this signal is issued on this instance. 8 | def signal_connect(signal : String, callback : Proc, data : Pointer(Void) = Pointer(Void).null) : LibVips::Gulong 9 | if (cb = callback.as?(Image::EvalProc)) 10 | em = LibVips::EvalSignal.new { |imgptr, progressptr, data| 11 | next if imgptr.null? || progressptr.null? 12 | img = Image.new(imgptr) 13 | progress = progressptr.value 14 | cb.call(img, progress) 15 | } 16 | callback = em 17 | end 18 | 19 | LibVips.g_signal_connect_data(@handle, signal, 20 | callback.pointer, 21 | data, 22 | nil, 23 | LibVips::GConnectFlags::After).tap do |ret| 24 | raise VipsException.new("Couldn't connect signal #{signal}") if ret == 0 25 | end 26 | end 27 | 28 | # Disconnects a handler from this object 29 | def signal_disconnect(handler_id : LibVips::Gulong) 30 | LibVips.g_signal_handler_disconnect(@handle, handler_id) unless handler_id == 0 31 | end 32 | 33 | # Disconnects all handlers from this object that match `func` and `data` 34 | def signal_disconnect(func : Proc, data : Void* = Pointer(Void).null) 35 | LibVips.g_signal_handlers_disconnect_matched(@handle, 36 | LibVips::GSignalMatchType::GSignalMatchFunc | 37 | LibVips::GSignalMatchType::GSignalMatchData, 38 | 0, 0, Pointer(LibVips::GClosure).null, nil, data) 39 | end 40 | 41 | # Disconnects all handlers from this object that match 42 | def signal_disconnect(data : LibVips::Gpointer) 43 | LibVips.g_signal_handlers_disconnect_matched(@handle, 44 | LibVips::GSignalMatchType::GSignalMatchData, 45 | 0, 0, Pointer(LibVips::GClosure).null, nil, data) 46 | end 47 | 48 | # Decreases the reference count of object. 49 | # When its reference count drops to 0, its memory is freed. 50 | def release_handle 51 | LibVips.g_object_unref(@handle) unless @handle.null? 52 | true 53 | end 54 | 55 | # Increases the reference count of object 56 | def object_ref 57 | LibVips.g_object_ref(@handle) 58 | end 59 | 60 | # Get the reference count of object. 61 | def ref_count 62 | @handle.value.ref_count 63 | end 64 | 65 | def get(name : String, gval : GValue) 66 | LibVips.g_object_get_property(@handle, name, gval) 67 | gval.get 68 | end 69 | 70 | def set(name : String, gval : GValue) 71 | LibVips.g_object_set_property(@handle, name, gval) 72 | end 73 | 74 | # :nodoc: 75 | def handle 76 | @handle 77 | end 78 | 79 | def finalize 80 | LibVips.g_object_unref(@handle) 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /src/vips/gvalue.cr: -------------------------------------------------------------------------------- 1 | module Vips 2 | # Class to wrap `LibVips::GValue` in a Crystal class. 3 | # This class wraps `LibVips::GValue` in a convenient interface. You can use 4 | # instances of this class to get and set `GObject` properties. 5 | # On construction, `LibVips::GValue` is all zero (empty). You can pass it to 6 | # a get function to have it filled by `GObject`, or use `initialize(gvalue)` to 7 | # set a type, `set` to set a value, then use it to set an object property. 8 | class GValue 9 | @value : LibVips::GValue 10 | 11 | # Initialize new instance of `GValue` 12 | def initialize 13 | @value = LibVips::GValue.new 14 | @initialized = false 15 | end 16 | 17 | # Initialize new instance with specified `GValue` 18 | def initialize(value : GValue) 19 | @value = value.@value 20 | @initialized = false 21 | end 22 | 23 | # Set the type of a GValue 24 | # GValues have a set type, fixed at creation time. Use this method to set 25 | # the type of GValue before assiging to it. 26 | # 27 | # GTypes are 32 or 64-bit integers (depending on platform). 28 | def set_type(type) : Nil 29 | LibVips.g_value_init(self, type) 30 | @initialized = true 31 | end 32 | 33 | # Set a GValue 34 | # The value is converted to the type of the GValue, if possible, and assigned 35 | def set(value) 36 | gtype = get_type 37 | fundamental = Vips.fundamental_type(gtype) 38 | if value.is_a?(Type) 39 | value = value.value 40 | end 41 | if gtype == GBool 42 | LibVips.g_value_set_boolean(self, (value ? 1 : 0)) 43 | elsif gtype == GInt 44 | LibVips.g_value_set_int(self, Converter.to_i32(value)) 45 | elsif gtype == GUint64 46 | LibVips.g_value_set_uint64(self, Converter.to_u64(value)) 47 | elsif gtype == GDouble 48 | LibVips.g_value_set_double(self, Converter.to_double(value)) 49 | elsif fundamental == GEnum 50 | LibVips.g_value_set_enum(self, Converter.to_i32(value)) 51 | elsif fundamental == GFlags 52 | LibVips.g_value_set_flags(self, Converter.to_u32(value)) 53 | elsif gtype == GString 54 | LibVips.g_value_set_string(self, Converter.to_string(value)) 55 | elsif gtype == VRefStr 56 | LibVips.vips_value_set_ref_string(self, Converter.to_string(value)) 57 | elsif fundamental == GObject && (obj = value.as?(Vips::GObject)) 58 | LibVips.g_value_set_object(self, obj.handle) 59 | elsif gtype == VArrayInt 60 | aval = value.is_a?(Array) ? value : [value] 61 | intarr = case aval 62 | when Array(Int32) then aval 63 | when Array then Array(Int32).new(aval.size) { |i| Converter.to_i32(aval[i]) } 64 | else 65 | raise VipsException.new("unsuported value type #{typeof(value)} for gtype #{Vips.typename(gtype)} ") 66 | end 67 | LibVips.vips_value_set_array_int(self, intarr, intarr.size) 68 | elsif gtype == VArrayDouble 69 | aval = value.is_a?(Array) ? value : [value] 70 | intarr = case aval 71 | when Array(Float64) then aval 72 | when Array then Array(Float64).new(aval.size) { |i| Converter.to_double(aval[i]) } 73 | else 74 | raise VipsException.new("unsuported value type #{typeof(value)} for gtype #{Vips.typename(gtype)} ") 75 | end 76 | LibVips.vips_value_set_array_double(self, intarr, intarr.size) 77 | elsif gtype == VArrayImage && (images = value.as?(Array(Image))) 78 | size = images.size 79 | LibVips.vips_value_set_array_image(self, size) 80 | ptr = LibVips.vips_value_get_array_image(self, out _) 81 | ptr.map_with_index!(size) { |_, i| images[i].object_ref.as(LibVips::VipsImage*) } 82 | elsif gtype == VBlob && (blob = value.as?(VipsBlob)) 83 | LibVips.g_value_set_boxed(self, blob) 84 | elsif gtype == VBlob 85 | mem = case value 86 | when String then value.to_slice 87 | when Array(Char) then String.new(value).to_slice 88 | when Bytes then value 89 | else 90 | raise VipsException.new("unsuported value type #{typeof(value)} for gtype #{Vips.typename(gtype)} ") 91 | end 92 | # We need to set the blob to a copy of the string that vips can own 93 | ptr = LibVips.g_malloc(mem.size) 94 | ptr.copy_from(mem.to_unsafe.as(Void*), mem.size) 95 | 96 | if Vips.at_least_libvips?(8, 6) 97 | LibVips.vips_value_set_blob_free(self, ptr, mem.size) 98 | else 99 | free = LibVips::VipsCallbackFn.new do |a, b| 100 | LibVips.g_free(a) 101 | 0 102 | end 103 | 104 | LibVips.vips_value_set_blob(self, free, ptr, mem.size) 105 | end 106 | else 107 | raise VipsException.new("unsupported gtype for set #{Vips.typename(gtype)}, fundamental #{Vips.typename(fundamental)}, value type #{typeof(value)}") 108 | end 109 | end 110 | 111 | # Get the contents of a GValue 112 | # The contents of the GValue are read out as a Crystal type 113 | def get : Type 114 | gtype = get_type 115 | fundamental = Vips.fundamental_type(gtype) 116 | result = 117 | if gtype == GBool 118 | LibVips.g_value_get_boolean(self) 119 | elsif gtype == GInt 120 | LibVips.g_value_get_int(self) 121 | elsif gtype == GUint64 122 | LibVips.g_value_get_uint64(self) 123 | elsif gtype == GDouble 124 | LibVips.g_value_get_double(self) 125 | elsif fundamental == GEnum 126 | LibVips.g_value_get_enum(self) 127 | elsif fundamental == GFlags 128 | LibVips.g_value_get_flags(self) 129 | elsif gtype == GString 130 | String.new(LibVips.g_value_get_string(self) || Bytes.empty) 131 | elsif gtype == VRefStr 132 | res = LibVips.vips_value_get_ref_string(self, out size) 133 | String.new(res, size) 134 | elsif gtype == VImageType 135 | # g_value_get_object will not add a ref ... that is 136 | # held by the gvalue 137 | vi = LibVips.g_value_get_object(self) 138 | 139 | # we want a ref that will last with the life of the vi: 140 | # this ref is matched by the unref 141 | image = Image.new(vi.as(LibVips::VipsImage*)) 142 | image.object_ref 143 | image 144 | elsif gtype == VArrayInt 145 | ptr = LibVips.vips_value_get_array_int(self, out vsize) 146 | Array(Int32).new(vsize) { |i| ptr[i] } 147 | elsif gtype == VArrayDouble 148 | ptr = LibVips.vips_value_get_array_double(self, out dsize) 149 | Array(Float64).new(dsize) { |i| ptr[i] } 150 | elsif gtype == VArrayImage 151 | ptr = LibVips.vips_value_get_array_image(self, out isize) 152 | Array(Image).new(isize) do |i| 153 | image = Image.new(ptr[i]) 154 | image.object_ref 155 | image 156 | end 157 | elsif gtype == VBlob 158 | ptr = LibVips.vips_value_get_blob(self, out bsize) 159 | # Blob types are returned as an array of bytes. 160 | res = Bytes.new(bsize) 161 | res.to_unsafe.copy_from(ptr.as(UInt8*), bsize.to_i) 162 | res 163 | else 164 | raise VipsException.new("unsupported gtype for get #{Vips.typename(gtype)}, fundamental #{Vips.typename(fundamental)}") 165 | end 166 | Type.new(result) 167 | end 168 | 169 | # Get the GType of this GValue 170 | def get_type 171 | @value.g_type 172 | end 173 | 174 | # :nodoc: 175 | def finalize 176 | return unless @initialized 177 | LibVips.g_value_unset(self) 178 | end 179 | 180 | # :nodoc: 181 | def to_unsafe 182 | pointerof(@value) 183 | end 184 | 185 | # The fundamental type corresponding to gboolean 186 | GBool = Vips.type_from_name("gboolean") 187 | # The fundamental type corresponding to gint 188 | GInt = Vips.type_from_name("gint") 189 | # The fundamental type corresponding to guint64 190 | GUint64 = Vips.type_from_name("guint64") 191 | # The fundamental type from which all enumeration types are derived 192 | GEnum = Vips.type_from_name("GEnum") 193 | # The fundamental type from which all flags types are derived 194 | GFlags = Vips.type_from_name("GFlags") 195 | # The fundamental type corresponding to gdouble 196 | GDouble = Vips.type_from_name("gdouble") 197 | # The fundamental type corresponding to null-terminated C strings. 198 | GString = Vips.type_from_name("gchararray") 199 | # The fundamental type for GObject 200 | GObject = Vips.type_from_name("GObject") 201 | # The fundamental type for VipsImage 202 | VImageType = Vips.type_from_name("VipsImage") 203 | # The fundamental type for VipsArrayInt 204 | VArrayInt = Vips.type_from_name("VipsArrayInt") 205 | # The fundamental type for VipsArrayDouble 206 | VArrayDouble = Vips.type_from_name("VipsArrayDouble") 207 | # The fundamental type for VipsArrayImage 208 | VArrayImage = Vips.type_from_name("VipsArrayImage") 209 | # The fundamental type for VipsRefString 210 | VRefStr = Vips.type_from_name("VipsRefString") 211 | # The fundamental type for VipsBlob 212 | VBlob = Vips.type_from_name("VipsBlob") 213 | # The fundamental type for VipsBandFormat 214 | VBandFormat = LibVips.vips_band_format_get_type 215 | # The fundamental type for VipsBlendMode 216 | VBlendMode = Vips.at_least_libvips?(8, 6) ? LibVips.vips_blend_mode_get_type : 0 217 | # The fundamental type for VipsSource 218 | VSource = Vips.at_least_libvips?(8, 9) ? Vips.type_from_name("VipsSource") : 0 219 | # The fundamental type for VipsTarget 220 | VTarget = Vips.at_least_libvips?(8, 9) ? Vips.type_from_name("VipsTarget") : 0 221 | end 222 | 223 | # :nodoc: 224 | module Converter 225 | extend self 226 | 227 | def to_i32(value) 228 | if (v = value) && (v.responds_to?(:to_i)) 229 | v.to_i 230 | else 231 | 0 232 | end 233 | end 234 | 235 | def to_u32(value) 236 | if (v = value) && (v.responds_to?(:to_u32)) 237 | v.to_u32 238 | else 239 | 0_u32 240 | end 241 | end 242 | 243 | def to_u64(value) 244 | if (v = value) && (v.responds_to?(:to_u64)) 245 | v.to_u64 246 | else 247 | 0_u64 248 | end 249 | end 250 | 251 | def to_double(value) 252 | if (v = value) && (v.responds_to?(:to_f)) 253 | v.to_f 254 | else 255 | 0.0 256 | end 257 | end 258 | 259 | def to_string(value) 260 | if (v = value) && (v.responds_to?(:to_s)) 261 | v.to_s 262 | else 263 | "" 264 | end 265 | end 266 | end 267 | end 268 | -------------------------------------------------------------------------------- /src/vips/interpolate.cr: -------------------------------------------------------------------------------- 1 | module Vips 2 | # Make interpolators for operators like `Image#affine` 3 | class Interpolate < VipsObject 4 | private def initialize(@ihandle : LibVips::VipsInterpolate*) 5 | super(@ihandle.as(LibVips::VipsObject*)) 6 | end 7 | 8 | # Make a new interpolator by name. 9 | # Make a new interpolator from the libvips class nickname. For example: 10 | # 11 | # ``` 12 | # inter = Vips::Interpolate.new_from_name("bicubic") 13 | # ``` 14 | # You can get a list of all supported interpolators from the command-line with 15 | # 16 | # ```sh 17 | # vips -l interpolate 18 | # ``` 19 | # See for example `Image#affine` 20 | def self.new_from_name(name : String) 21 | vi = LibVips.vips_interpolate_new(name) 22 | raise VipsException.new("no such interpolator #{name}") if vi.null? 23 | new(vi) 24 | end 25 | 26 | # :nodoc: 27 | def to_unsafe 28 | @ihandle 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /src/vips/introspect.cr: -------------------------------------------------------------------------------- 1 | module Vips 2 | # Build introspection data for operations 3 | # Make an operation, introspect it, and build a structure representing 4 | # everything we know about it. 5 | class Introspect 6 | @@cache = Hash(String, Introspect).new 7 | # An object structure that encapsulate the metadata 8 | # required to specify arguments 9 | record Argument, name : String, flags : LibVips::VipsArgumentFlags, type : LibVips::GType 10 | 11 | # The first required input image or nil 12 | getter! member_x : Argument? 13 | 14 | # A bool indicating if this operation is mutable 15 | getter mutable : Bool = false 16 | 17 | # The required input for this operation 18 | getter required_input : Array(Argument) = Array(Argument).new 19 | 20 | # The optional input for this operation 21 | getter optional_input : Hash(String, Argument) = Hash(String, Argument).new 22 | 23 | # The required output for this operation 24 | getter required_output : Array(Argument) = Array(Argument).new 25 | 26 | # The optional output for this operation 27 | getter optional_output : Hash(String, Argument) = Hash(String, Argument).new 28 | 29 | protected def initialize(operation_name : String) 30 | op = Operation.new(operation_name) 31 | args = get_args(op) 32 | 33 | args.each do |arg| 34 | name = arg.first 35 | flag = arg.last 36 | gtype = op.get_typeof(name).not_nil! 37 | 38 | details = Argument.new(name, flag, gtype) 39 | 40 | if (flag & LibVips::VipsArgumentFlags::Input).value != 0 41 | if ((flag & LibVips::VipsArgumentFlags::Required).value != 0) && 42 | ((flag & LibVips::VipsArgumentFlags::Deprecated).value == 0) 43 | # the first required input image arg will be self 44 | if (@member_x.nil? && gtype == GValue::VImageType) 45 | @member_x = details 46 | else 47 | required_input << details 48 | end 49 | else 50 | # we allow deprecated optional args 51 | optional_input[name] = details 52 | end 53 | 54 | # modified input arguments count as mutable 55 | if (flag & LibVips::VipsArgumentFlags::Modify).value != 0 && 56 | (flag & LibVips::VipsArgumentFlags::Required).value != 0 && 57 | (flag & LibVips::VipsArgumentFlags::Deprecated).value == 0 58 | @mutable = true 59 | end 60 | elsif (flag & LibVips::VipsArgumentFlags::Output).value != 0 61 | if (flag & LibVips::VipsArgumentFlags::Required).value != 0 && 62 | (flag & LibVips::VipsArgumentFlags::Deprecated).value == 0 63 | required_output << details 64 | else 65 | # again, allow deprecated optional args 66 | optional_output[name] = details 67 | end 68 | end 69 | end 70 | end 71 | 72 | # Get all arguments for an operation. 73 | def get_args(op : Operation) 74 | args = Array(Tuple(String, LibVips::VipsArgumentFlags)).new 75 | 76 | add_arg = ->(name : String, flags : LibVips::VipsArgumentFlags) { 77 | # libvips uses '-' to separate parts of arg names, but we 78 | # need '_' for crystal 79 | name = name.gsub('-', '_') 80 | args << {name, flags} 81 | nil 82 | } 83 | 84 | # vips_object_get_args was added in 8.7 85 | if Vips.at_least_libvips?(8, 7) 86 | result = LibVips.vips_object_get_args(op.to_obj, out names, out flags_, out count) 87 | raise VipsException.new("unable to get arguments for operation") unless result == 0 88 | 89 | 0.upto(count - 1) do |i| 90 | flag = LibVips::VipsArgumentFlags.from_value(flags_[i]) 91 | next if (flag & LibVips::VipsArgumentFlags::Construct).value == 0 92 | name = String.new(names[i]) 93 | add_arg.call(name, flag) 94 | end 95 | else 96 | proc = LibVips::VipsArgumentMapFn.new do |_self, pspec, argcls, arginst, a, b| 97 | flag = argcls.value.flags 98 | next Pointer(Void).null if (flag & LibVips::VipsArgumentFlags::Construct).value == 0 99 | 100 | name = String.new(pspec.value.name) 101 | handler = Box(Proc(String, LibVips::VipsArgumentFlags, Nil)).unbox(a) 102 | handler.call(name, flag) 103 | Pointer(Void).null 104 | end 105 | 106 | LibVips.vips_argument_map(op.to_obj, proc, Box.box(add_arg), Pointer(Void).null) 107 | end 108 | 109 | args 110 | end 111 | 112 | # Get introspection data for a specified operation name. 113 | def self.get(operation_name : String) 114 | @@cache[operation_name] ||= Introspect.new(operation_name) 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /src/vips/mutableimage.cr: -------------------------------------------------------------------------------- 1 | require "math" 2 | 3 | module Vips 4 | class MutableImage < Image 5 | # :nodoc: 6 | getter image : Image 7 | 8 | protected def initialize(image : Image) 9 | # We take a copy of the regular Image to ensure we have an unshared 10 | # (unique) object. We forward things like #width and #height to this, and 11 | # it's the thing we return at the end of the mutate block. 12 | @image = image.copy 13 | super(@image.object_ref.as(LibVips::VipsImage*)) 14 | end 15 | 16 | # Sets the type and value of an item of metadata. Any old item of the 17 | # same name is removed. See `GValue` for types 18 | def set(gtype : LibVips::GType, name : String, value) 19 | gv = GValue.new 20 | gv.set_type(gtype) 21 | gv.set(value) 22 | LibVips.vips_image_set(self, name, gv) 23 | end 24 | 25 | # Sets the value of an item of metadata. The metadata item must already exists 26 | def set(name : String, value) 27 | gtype = get_typeof(name) 28 | raise VipsException.new("metadata item #{name} does not exist - use the set(gtype,name,value) overload to create and set") if gtype == 0 29 | set(gtype, name, value) 30 | end 31 | 32 | # Remove a metadata item from an image. 33 | # named metadata item is removed 34 | def remove(name : String) 35 | LibVips.vips_image_remove(self, name) 36 | end 37 | 38 | # Use `[]` to set band elements on an image. For example 39 | # 40 | # ``` 41 | # img = image.mutate { |x| x[1] = green } 42 | # ``` 43 | # will change band 1 ( the middle band) 44 | def []=(index, value) 45 | nleft = Math.min(bands, Math.max(0, index)) 46 | nright = Math.min(bands, Math.max(0, bands - 1 - i)) 47 | offset = bands - nright 48 | left = nleft > 0 ? image.extract_band(0, n: nleft) : nil 49 | right = nright > 0 ? image.extract_band(offset, n: nright) : nil 50 | if left.nil? 51 | @image = value.bandjoin(right.not_nil!) 52 | elsif right.nil? 53 | @image = left.not_nil!.bandjoin(value) 54 | else 55 | image = left.not_nil!.bandjoin(value, right.not_nil!) 56 | end 57 | end 58 | 59 | def mutate 60 | yield self 61 | image 62 | end 63 | 64 | def to_s(io : IO) 65 | io << "" 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /src/vips/operation.cr: -------------------------------------------------------------------------------- 1 | module Vips 2 | class Operation < VipsObject 3 | @ophandle : LibVips::VipsOperation* 4 | 5 | def initialize(@ophandle) 6 | super(@ophandle.as(LibVips::VipsObject*)) 7 | end 8 | 9 | # Creates a new `VisOperation` with the specified nickname 10 | # You'll need to set any arguments and build the operation before you can use it. 11 | def self.new(operation_name : String) 12 | op = LibVips.vips_operation_new(operation_name) 13 | raise VipsException.new("no such operation #{operation_name}") if op.null? 14 | new(op) 15 | end 16 | 17 | def self.build(operation : Operation) 18 | op = LibVips.vips_cache_operation_build(operation) 19 | if op.null? 20 | LibVips.vips_object_unref_outputs(operation.to_obj) 21 | raise VipsException.new("unable to call operation") 22 | end 23 | 24 | new(op) 25 | end 26 | 27 | # Set a GObject property. The value is converted to the property type, if possible. 28 | protected def set(gtype : LibVips::GType, match_image : Image?, name : String, value) 29 | # If the object wants an image and we have a constant, Imageize it. 30 | # 31 | # If the objects wants an image array, Imageize any constants in the array 32 | if (image = match_image) 33 | if gtype == GValue::VImageType 34 | value = Image.imageize(image, value) 35 | elsif gtype == GValue::VArrayImage 36 | raise VipsException.new("unsupported value type #{typeof(value)} for VipsArrayImage") unless value.is_a?(Array) 37 | # value = Array(Image).new(value.size) {|i| Image.imageize(match_image, i)} 38 | images = Array(Image).new(value.size) 39 | value.each do |v| 40 | images << Image.imageize(match_image, v) 41 | end 42 | value = images 43 | end 44 | end 45 | set(gtype, name, value) 46 | end 47 | 48 | # Lookup the set of flags for this operation 49 | def flags 50 | LibVips.vips_operation_get_flags(self) 51 | end 52 | 53 | def self.call(operation_name : String, kwargs : Optional?, match_image : Image?, *args) 54 | # pull out the special string_options kwarg 55 | str_options = kwargs.try &.delete("string_options").try &.as_s 56 | intro = Introspect.get(operation_name) 57 | if intro.required_input.size != args.size 58 | raise VipsException.new("unable to call #{operation_name}: #{args.size} arguments given, but #{intro.required_input.size} required") 59 | end 60 | 61 | if !intro.mutable && match_image.is_a?(MutableImage) 62 | raise VipsException.new("unable to call #{operation_name}: operation must be mutable") 63 | end 64 | 65 | begin 66 | op = new(operation_name) 67 | # set any string options before any args so they can't be overriden 68 | if (stropt = str_options) && !op.set(stropt) 69 | raise VipsException.new("unable to call #{operation_name}") 70 | end 71 | 72 | # set all required inputs 73 | if (mi = match_image) && (mx = intro.member_x?) 74 | op.set(mx.type, mx.name, mi) 75 | end 76 | 77 | intro.required_input.each_with_index { |arg, i| op.set(arg.type, match_image, arg.name, args[i]) } 78 | 79 | # Set all optional inputs, if any 80 | if (kw = kwargs) 81 | kw.each do |key, val| 82 | if (arg = intro.optional_input[key]?) 83 | op.set(arg.type, match_image, key, val) 84 | elsif !intro.optional_output.has_key?(key) 85 | raise VipsException.new("#{operation_name} does not support optional argument: #{key}") 86 | end 87 | end 88 | end 89 | end 90 | 91 | # build operation 92 | vop = build(op) 93 | results = Array(Type).new(intro.required_output.size) 94 | begin 95 | # get all required results 96 | intro.required_output.each { |oarg| results << vop.get(oarg.name) } 97 | 98 | # fetch optional output args, if any 99 | if (kw = kwargs) 100 | optarg = Optional.new 101 | kw.each do |k, _| 102 | if intro.optional_output.has_key?(k) 103 | optarg[k] = vop.get(k) 104 | end 105 | end 106 | unless optarg.empty? 107 | results << Type.new(optarg) 108 | end 109 | 110 | LibVips.vips_object_unref_outputs(op.to_obj) 111 | end 112 | 113 | results.size == 1 ? results.first : results 114 | end 115 | end 116 | 117 | def self.call(operation_name : String, kwargs : Optional, *args) 118 | call(operation_name, kwargs, nil, *args) 119 | end 120 | 121 | def self.call(operation_name : String, *args) 122 | call(operation_name, nil, nil, *args) 123 | end 124 | 125 | # :nodoc: 126 | def to_unsafe 127 | @ophandle 128 | end 129 | 130 | def to_obj 131 | @ohandle 132 | end 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /src/vips/region.cr: -------------------------------------------------------------------------------- 1 | module Vips 2 | # Wrap libvips VipsRegion object. 3 | # A region is a small part of an image. You use regions to read pixels 4 | # out of images without storing the entire image in memory. 5 | # Note: At least libvips 8.8 is needed. 6 | class Region < VipsObject 7 | private def initialize(@rhandle : LibVips::VipsRegion*) 8 | super(@rhandle.as(LibVips::VipsObject*)) 9 | end 10 | 11 | # Make a region on an image 12 | def self.new(image : Image) 13 | vi = LibVips.vips_region_new(image).tap do |ret| 14 | raise VipsException.new("unable to make region") if ret.null? 15 | end 16 | new(vi) 17 | end 18 | 19 | # width of pixels held by region 20 | def width : Int32 21 | LibVips.vips_region_width(self) 22 | end 23 | 24 | # height of pixels held by region 25 | def height : Int32 26 | LibVips.vips_region_height(self) 27 | end 28 | 29 | # Fetch an area of pixels. 30 | # *left* Left edge of area to fetch. 31 | # *top* Top edge of area to fetch. 32 | # *width* Width of area to fetch. 33 | # *height* Height of area to fetch. 34 | # Returns `Bytes` filled with pixel data. 35 | def fetch(left : Int32, top : Int32, width : Int32, height : Int32) : Bytes 36 | ptr = LibVips.vips_region_fetch(self, left, top, width, height, out size).tap do |ret| 37 | raise "unable to fetch from region" if ret.null? 38 | end 39 | 40 | result = Bytes.new(size) 41 | ptr.copy_to(result.to_unsafe, size) 42 | Vips.free(ptr.as(Void*)) 43 | result 44 | end 45 | 46 | # :nodoc: 47 | def to_unsafe 48 | @rhandle 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /src/vips/source.cr: -------------------------------------------------------------------------------- 1 | module Vips 2 | # Input connection. For example 3 | # ``` 4 | # source = Vips::Source.new_from_file("k2.jpg") 5 | # image = Vips::Image.new_from_source(source) 6 | # ``` 7 | class Source < Connection 8 | protected def initialize(@shandle : LibVips::VipsSource*) 9 | super(@shandle.as(LibVips::VipsConnection*)) 10 | end 11 | 12 | # Create a new source from a file descriptor. File descriptors are 13 | # small integers, for example 0 is stdin. 14 | # 15 | # Pass sources to `Image.new_from_source` to load images from 16 | # them. 17 | def self.new_from_descriptor(descriptor : Int) 18 | ptr = LibVips.vips_source_new_from_descriptor(descriptor) 19 | raise VipsException.new("can't create source from descriptor #{descriptor}") if ptr.null? 20 | new(ptr) 21 | end 22 | 23 | # Create a new source from a file name. 24 | # 25 | # Pass sources to `Image.new_from_source` to load images from 26 | # them. 27 | def self.new_from_file(filename : String) 28 | ptr = LibVips.vips_source_new_from_file(filename) 29 | raise VipsException.new("can't create source from filename #{filename}") if ptr.null? 30 | 31 | new(ptr) 32 | end 33 | 34 | # Create a new source from an area of memory. Memory areas can be 35 | # String, Bytes, or IO 36 | # 37 | # Pass sources to `Image.new_from_source` to load images from 38 | # them. 39 | def self.new_from_memory(data : String | Bytes | IO) 40 | buff = case data 41 | when String then data.to_slice 42 | when IO then data.gets_to_end.to_slice 43 | else data 44 | end 45 | 46 | ptr = LibVips.vips_source_new_from_memory(Box.box(buff), buff.bytesize) 47 | raise VipsException.new("can't create source from memory #{data}") if ptr.null? 48 | @@references << buff 49 | new(ptr) 50 | end 51 | 52 | def finalize 53 | @@references.clear 54 | end 55 | 56 | # :nodoc: 57 | def to_unsafe 58 | @shandle 59 | end 60 | 61 | # keep reference to bytes which are passed in new_from_memory 62 | @@references : Array(Bytes) = Array(Bytes).new 63 | end 64 | 65 | # A source you can attach action signal handlers to to implement 66 | # custom input types. 67 | # 68 | # For example: 69 | # 70 | # ``` 71 | # file = File.open("some/file/name", "rb") 72 | # source = Vips::SourceCustom.new 73 | # source.on_read { |slice| file.read(slice) } 74 | # image = Vips::Image.new_from_source(source) 75 | # ``` 76 | # 77 | # (just an example -- of course in practice you'd use `Source#new_from_file` 78 | # to read from a named file) 79 | class SourceCustom < Source 80 | @@box : Array(Pointer(Void)?) = Array(Pointer(Void)?).new 81 | 82 | def initialize 83 | raise VipsException.new("At least libvips 8.9 is needed for SourceCustom") unless Vips.at_least_libvips?(8, 9) 84 | ptr = LibVips.vips_source_custom_new 85 | raise VipsException.new("unable to vips_source_custome_new") if ptr.null? 86 | super(ptr.as(LibVips::VipsSource*)) 87 | end 88 | 89 | # The block is executed to read data from the source. The interface is 90 | # exactly as IO::read, ie. it takes a slice and reads atmost `slice.size` and 91 | # returns a number of bytes read from the source, or 0 if the source is already 92 | # at end of file. 93 | def on_read(&block : Bytes -> Int32) 94 | boxed_data = Box.box(block) 95 | @@box << boxed_data 96 | 97 | signal_connect("read", LibVips::ReadCB.new { |_source, buff, size, data| 98 | next 0 if size <= 0 99 | callback = Box(typeof(block)).unbox(data) 100 | slice = Bytes.new(buff.as(UInt8*), size - 1) 101 | callback.call(slice) 102 | }, boxed_data) 103 | end 104 | 105 | # The block is executed to seek the source. The interface is exactly as 106 | # IO::seek, ie. it should take an offset and whence, and return the 107 | # new read position. 108 | # 109 | # This handler is optional -- if you do not attach a seek handler, 110 | # `Source` will treat your source like an unseekable pipe object and 111 | # do extra caching. 112 | def on_seek(&block : (Int64, IO::Seek) -> Int64) 113 | boxed_data = Box.box(block) 114 | @@box << boxed_data 115 | 116 | signal_connect("seek", LibVips::SeekCB.new { |_source, offset, whence, data| 117 | callback = Box(typeof(block)).unbox(data) 118 | ret = callback.call(offset, IO::Seek.from_value(whence)) 119 | ret.to_i64 120 | }, boxed_data) 121 | end 122 | 123 | def finalize 124 | @@box.clear 125 | end 126 | end 127 | 128 | # Source connected to a readable `IO` 129 | class SourceStream < SourceCustom 130 | protected def initialize(@io : IO) 131 | super() 132 | on_read &->@io.read(Bytes) 133 | on_seek &->do_seek(Int64, IO::Seek) 134 | end 135 | 136 | def self.new_from_stream(io : IO) 137 | raise VipsException.new("The stream should be readable") if io.closed? 138 | new(io) 139 | end 140 | 141 | private def do_seek(offset, whence) 142 | val = @io.seek(offset, whence) rescue nil 143 | return -1_i64 if val.nil? 144 | @io.pos 145 | end 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /src/vips/stats.cr: -------------------------------------------------------------------------------- 1 | require "./lib" 2 | 3 | # `Stats` module provides the statistics of memory usage and opened files. 4 | # libvips watches the total amount of live tracked memory and 5 | # uses this information to decide when to trim caches. 6 | module Vips 7 | module Stats 8 | extend self 9 | 10 | # Get the number of active allocations. 11 | def allocations : Int 12 | LibVips.vips_tracked_get_allocs 13 | end 14 | 15 | # Get the number of bytes currently allocated `vips_malloc()` and friends. 16 | # libvips uses this figure to decide when to start dropping cache. 17 | def mem : Int 18 | LibVips.vips_tracked_get_mem 19 | end 20 | 21 | # Returns the largest number of bytes simultaneously allocated via vips_tracked_malloc(). 22 | # Handy for estimating max memory requirements for a program. 23 | def mem_highwater : Int 24 | LibVips.vips_tracked_get_mem_highwater 25 | end 26 | 27 | # Get the number of open files. 28 | def open_files : Int 29 | LibVips.vips_tracked_get_files 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /src/vips/target.cr: -------------------------------------------------------------------------------- 1 | module Vips 2 | # an output connection 3 | class Target < Connection 4 | protected def initialize(@thandle : LibVips::VipsTarget*) 5 | super(@thandle.as(LibVips::VipsConnection*)) 6 | end 7 | 8 | # Make a new target to write to a file descriptor (a small integer). 9 | # ``` 10 | # target = Vips::Target.new_to_descriptor(STDOUT) 11 | # ``` 12 | # Makes a descriptor attached to `STDOUT`. 13 | # 14 | # You can pass this target to (for example) `write_to_target` 15 | def self.new_to_descriptor(descriptor : Int32) 16 | ptr = LibVips.vips_target_new_to_descriptor(descriptor).tap do |ret| 17 | raise VipsException.new("can't create output target to descriptor #{descriptor}") if ret.null? 18 | end 19 | new(ptr) 20 | end 21 | 22 | # Make a new target to write to a file. 23 | # ``` 24 | # target = Vips::Target.new_to_file("myfile.jpg") 25 | # ``` 26 | # 27 | # You can pass this target to (for example) `write_to_target` 28 | def self.new_to_file(filename : String) 29 | ptr = LibVips.vips_target_new_to_file(filename).tap do |ret| 30 | raise VipsException.new("can't create output target to file #{filename}") if ret.null? 31 | end 32 | new(ptr) 33 | end 34 | 35 | # Make a new target to write to an area of memory. 36 | # ``` 37 | # target = Vips::Target.new_to_memory 38 | # ``` 39 | # 40 | # You can pass this target to (for example) `write_to_target` 41 | # 42 | # After writing to target, fetch the bytes from the target object with: 43 | # 44 | # ``` 45 | # bytes = target.blob 46 | # ``` 47 | def self.new_to_memory 48 | ptr = LibVips.vips_target_new_to_memory.tap do |ret| 49 | raise VipsException.new("can't create output target to memory") if ret.null? 50 | end 51 | new(ptr) 52 | end 53 | 54 | # Get the memory object held by the target when using `new_to_memory` 55 | def blob : Bytes 56 | get("blob").as_bytes 57 | end 58 | 59 | # :nodoc: 60 | def to_unsafe 61 | @thandle 62 | end 63 | end 64 | 65 | # `Target` you can connect handlers to implement behavior. 66 | class TargetCustom < Target 67 | @@box : Array(Pointer(Void)?) = Array(Pointer(Void)?).new 68 | 69 | def initialize 70 | raise VipsException.new("At least libvips 8.9 is needed for TargetCustom") unless Vips.at_least_libvips?(8, 9) 71 | ptr = LibVips.vips_target_custom_new 72 | raise VipsException.new("unable to vips_target_custom_new") if ptr.null? 73 | super(ptr.as(LibVips::VipsTarget*)) 74 | end 75 | 76 | # The block is executed to write data to the target. The interface is 77 | # exactly as IO::write, ie. it should write the bytes and return the 78 | # number of bytes written. 79 | def on_write(&block : Bytes -> Int64) 80 | boxed_data = Box.box(block) 81 | @@box << boxed_data 82 | 83 | signal_connect("write", LibVips::WriteCB.new { |source, buff, size, data| 84 | next -1_i64 if size <= 0 85 | callback = Box(typeof(block)).unbox(data) 86 | slice = Bytes.new(buff.as(UInt8*), size) 87 | @@box.delete(data) 88 | callback.call(slice) 89 | }, boxed_data) 90 | end 91 | 92 | # The block is executed at the end of write. It should do any necessary 93 | # finishing action, such as closing a file or flushing IO 94 | def on_finish(&block : ->) 95 | boxed_data = Box.box(block) 96 | @@box << boxed_data 97 | 98 | signal_connect("finish", LibVips::FinishCB.new { |_source, data| 99 | callback = Box(typeof(block)).unbox(data) 100 | callback.call 101 | @@box.delete(data) 102 | nil 103 | }, boxed_data) 104 | end 105 | end 106 | 107 | # Target connected to a writeable `IO` 108 | class TargetStream < TargetCustom 109 | protected def initialize(@io : IO) 110 | super() 111 | on_write &->(slice : Bytes) { 112 | @io.write(slice) 113 | slice.size.to_i64 114 | } 115 | on_finish &->{ @io.flush } 116 | end 117 | 118 | def self.new_from_stream(io : IO) 119 | raise VipsException.new("The stream should be write") if io.closed? 120 | new(io) 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /src/vips/vips.cr: -------------------------------------------------------------------------------- 1 | module Vips 2 | class VipsException < Exception 3 | def initialize(message) 4 | super("#{message}\n#{vips_error}") 5 | end 6 | 7 | private def vips_error 8 | err = String.new(LibVips.vips_error_buffer || Bytes.empty) 9 | Vips.clear_error 10 | err 11 | end 12 | end 13 | 14 | # Starts up the world of VIPS. 15 | # this function is automatically called 16 | def self.init 17 | @@initialized ||= LibVips.vips_init("CrystalVips") == 0 18 | raise VipsException.new("unable to initialize libvips") unless @@initialized 19 | @@initialized 20 | end 21 | 22 | def self.shutdown 23 | LibVips.vips_shutdown 24 | end 25 | 26 | def self.leak=(@@leak) 27 | LibVips.vips_leak_set(@@leak) 28 | end 29 | 30 | def self.profile(@@profile) 31 | LibVips.vips_profile_set(@@profile) 32 | end 33 | 34 | # Returns the number of worker threads that vips uses for image evaluation. 35 | def self.concurrency 36 | LibVips.vips_concurrency_get 37 | end 38 | 39 | # Set the size of the pools of worker threads vips uses for image evaluation. 40 | def self.concurrency=(value : Int) 41 | value = default_concurrency unless value > 0 42 | LibVips.vips_concurrency_set(value) 43 | end 44 | 45 | # Get the major, minor or patch version number of the libvips library. 46 | # Pass 0 to get the major version number 47 | # 1 to get minor, 2 to get patch. 48 | def self.version(flag : Int) 49 | raise ArgumentError.new("Flag must be in the range of 0 to 2") unless (0..2).includes?(flag) 50 | LibVips.vips_version(flag).tap do |v| 51 | raise VipsException.new("Unable to get library version") if v < 0 52 | end 53 | end 54 | 55 | # Returns version of libvips in 3-byte integer 56 | def self.version 57 | raise VipsException.new("Unable to initialize libvips") unless initialized 58 | value = 0 59 | 0.upto(2) do |flag| 60 | if flag == 0 61 | value = version(flag) 62 | else 63 | value = (value << 8) + version(flag) 64 | end 65 | end 66 | value 67 | end 68 | 69 | # Returns version string of libvips 70 | def self.version_string 71 | String.new(LibVips.vips_version_string) 72 | end 73 | 74 | # Returns if SIMD and the run-time compiler is enabled or not 75 | def self.vector? 76 | LibVips.vips_vector_isenabled == 1 77 | end 78 | 79 | # Enable SIMD and the run-time compiler. 80 | # This can give a nice speed-up, but can also be unstable on 81 | # some systems or with some versions of the run-time compiler. 82 | def self.vector=(val : Bool) 83 | LibVips.vips_vector_set_enabled(val) 84 | end 85 | 86 | # Is this at least libvips major.minor.[.patch]? 87 | def self.at_least_libvips?(x : Int, y : Int, z = 0) 88 | major = version(0) 89 | minor = version(1) 90 | patch = version(2) 91 | 92 | major > x || major == x && minor > y || 93 | major == x && minor == y && patch >= z 94 | end 95 | 96 | # Get a list of all the filename suffixes supported by libvips 97 | # Note: At least libvips 8.8 is needed 98 | def self.get_suffixes 99 | names = [] of String 100 | return names unless at_least_libvips?(8, 8) 101 | 102 | ptr = LibVips.vips_foreign_get_suffixes 103 | count = 0 104 | while (strptr = (ptr + count).value) 105 | names << String.new(strptr) 106 | LibVips.g_free(strptr) 107 | count += 1 108 | end 109 | LibVips.g_free(ptr) 110 | names.uniq!.sort! 111 | end 112 | 113 | # Reports leaks (hopefully there are none) it also tracks and reports peak memory use. 114 | def self.report_leaks 115 | LibVips.vips_object_print_all 116 | puts "memory: #{Stats.allocations} allocations, #{Stats.mem} bytes" 117 | puts "files: #{Stats.open_files} open" 118 | puts "memory: high-water mark: #{Stats.mem_highwater}" 119 | errbuf = String.new(LibVips.vips_error_buffer) 120 | puts "error buffer: #{errbuf}" unless errbuf.blank? 121 | end 122 | 123 | # Get the GType for a name. 124 | # Looks up the GType for a nickname. Types below basename in the type 125 | # hierarchy are searched. 126 | def self.typefind(basename : String, nickname : String) 127 | LibVips.vips_type_find(basename, nickname) 128 | end 129 | 130 | # Returns the name for a GType 131 | def self.typename(type : LibC::ULong) 132 | String.new(LibVips.g_type_name(type) || Bytes.empty) 133 | end 134 | 135 | # Return the nickname for a GType. 136 | def self.nickname(type : LibC::ULong) 137 | String.new(LibVips.vips_nickname_find(type) || Bytes.empty) 138 | end 139 | 140 | # Get a list of operations available within the libvips library. 141 | # This can be useful for documentation generators 142 | def self.get_operations 143 | nicknames = Array(String).new 144 | LibVips.vips_type_map(type_from_name("VipsOperation"), ->ops_cb, Box.box(nicknames), nil) 145 | nicknames.uniq!.sort! 146 | end 147 | 148 | # Get a list of enums available within the libvips library. 149 | def self.get_enums 150 | enums = Array(String).new 151 | LibVips.vips_type_map(type_from_name("GEnum"), ->enum_cb, Box.box(enums), nil) 152 | enums.sort! 153 | end 154 | 155 | # Get all values for a enum (GType). 156 | def self.enum_values(type : LibC::ULong) 157 | typecls = LibVips.g_type_class_ref(type) 158 | values = Hash(String, Int32).new 159 | return values if typecls.null? 160 | enumcls = typecls.as(Pointer(LibVips::GEnumClass)).value 161 | 162 | ptr = enumcls.values.as(Pointer(LibVips::GEnumValue)) 163 | # -1 since we always have "last" member 164 | 0.upto(enumcls.n_values - 2) do |i| 165 | enumval = ptr[i] 166 | values[String.new(enumval.value_nick)] = enumval.value 167 | end 168 | values 169 | end 170 | 171 | # Frees the memory pointed to by `mem` 172 | def self.free(mem : Void*) 173 | LibVips.g_free(mem) 174 | end 175 | 176 | # Return the GType for a name. 177 | def self.type_from_name(nickname : String) 178 | LibVips.g_type_from_name(nickname) 179 | end 180 | 181 | # Extract the fundamental type ID portion. 182 | def self.fundamental_type(type : LibC::ULong) 183 | LibVips.g_type_fundamental(type) 184 | end 185 | 186 | def self.clear_error 187 | LibVips.vips_error_clear 188 | end 189 | 190 | # Flag to tell if libvips has been initialized or not. 191 | # initialization will happen at the load of module and you should only call 192 | # `Vips#init` if auto initialization failed. 193 | class_getter? initialized = false 194 | 195 | # Enable or disable libvips leak checking. 196 | # When enabled, libvips will check for object and area leaks on exit. 197 | # Enabling this option will make libvips run slightly more slowly. 198 | class_property? leak = false 199 | 200 | # Enable or disable libvips profile recording. 201 | # If set, vips will record profiling information, and dump it on program 202 | # exit. These profiles can be analyzed with the `vipsprofile` program. 203 | class_property? profile = false 204 | 205 | # Track the original default concurrency so we can reset to it. 206 | class_getter default_concurrency : Int32 { LibVips.vips_concurrency_get } 207 | 208 | private def self.ops_cb(type, a, b) 209 | nm = nickname(type) 210 | arr = Box(Array(String)).unbox(a) 211 | 212 | # exclude base classes, for e.g. 'jpegload_base' 213 | if typefind("VipsOperation", nm) != 0 214 | arr << nm 215 | end 216 | LibVips.vips_type_map(type, ->ops_cb, a, b) 217 | end 218 | 219 | private def self.enum_cb(type, a, b) 220 | enm = typename(type) 221 | arr = Box(Array(String)).unbox(a) 222 | arr << enm 223 | LibVips.vips_type_map(type, ->enum_cb, a, b) 224 | end 225 | 226 | struct Type 227 | # :nodoc: 228 | alias VALTYPE = Int32 | Float64 | UInt64 | String | Bool | Bytes | Image | GObject | Array(Int32) | Array(Float64) | Array(Image) | Optional 229 | 230 | getter value : VALTYPE 231 | 232 | def initialize(@value) 233 | end 234 | 235 | def as_b 236 | return as_i32 > 0 if @value.is_a?(Number) 237 | @value.as(Bool) 238 | end 239 | 240 | def as_i32 241 | @value.is_a?(Number) ? @value.as(Number).to_i : @value.as(Int32) 242 | end 243 | 244 | def as_f64 245 | @value.is_a?(Number) ? @value.as(Number).to_f : @value.as(Float64) 246 | end 247 | 248 | def as_u64 249 | @value.is_a?(Number) ? @value.as(Number).to_u64 : @value.as(UInt64) 250 | end 251 | 252 | def as_s 253 | return @value.as(String) if @value.is_a?(String) 254 | @value.to_s 255 | end 256 | 257 | def as_bytes 258 | @value.as(Bytes) 259 | end 260 | 261 | def as_a32 262 | @value.as(Array(Int32)) 263 | end 264 | 265 | def as_a64 266 | @value.as(Array(Float64)) 267 | end 268 | 269 | def as_image 270 | @value.as(Image) 271 | end 272 | 273 | def as_aimg 274 | @value.as(Array(Image)) 275 | end 276 | 277 | def as_h 278 | @value.as(Optional) 279 | end 280 | 281 | def as_o 282 | @value.as(GObject) 283 | end 284 | 285 | def as_enum(cls : Enum.class) 286 | cls.from_value(as_i32) 287 | end 288 | end 289 | 290 | # :nodoc: 291 | class Optional 292 | def initialize(**opts) 293 | @val = Hash(String, Type).new 294 | opts.each do |k, v| 295 | # ignore nil values 296 | next if v.nil? 297 | 298 | if v.is_a?(Enum) 299 | v = v.value 300 | end 301 | @val[k.to_s] = Type.new(v) 302 | end 303 | end 304 | 305 | def []=(key : String, value) 306 | if value.is_a?(Enums::FailOn) 307 | Enums.add_failon(self, value) 308 | elsif value.is_a?(Type) 309 | @val[key] = value 310 | else 311 | @val[key] = Type.new(value) 312 | end 313 | end 314 | 315 | forward_missing_to @val 316 | end 317 | end 318 | 319 | Vips.init 320 | -------------------------------------------------------------------------------- /src/vips/vipsblob.cr: -------------------------------------------------------------------------------- 1 | require "./lib" 2 | 3 | module Vips 4 | class VipsBlob 5 | def initialize(@handle : LibVips::VipsBlob*) 6 | @blob = @handle.value 7 | end 8 | 9 | def get_data 10 | data = LibVips.vips_blob_get(self, out size) 11 | {data, size} 12 | end 13 | 14 | def length 15 | @blob.area.length 16 | end 17 | 18 | def ref_count 19 | @blob.area.count 20 | end 21 | 22 | def release 23 | return if @handle.null? 24 | LibVips.vips_area_unref(Box.box(@blob.area)) 25 | end 26 | 27 | def invalid? 28 | @handle.null? 29 | end 30 | 31 | # :nodoc: 32 | def to_unsafe 33 | @handle 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /src/vips/vipsobject.cr: -------------------------------------------------------------------------------- 1 | module Vips 2 | class VipsObject < GObject 3 | protected def initialize(@ohandle : LibVips::VipsObject*) 4 | super(@ohandle.as(LibVips::GObject*)) 5 | end 6 | 7 | def post_close(&block : ->) 8 | signal_connect("postclose", block) 9 | end 10 | 11 | # Print a table of all active libvips objects. Handy for debugging. 12 | def print_all 13 | LibVips.vips_object_print_all 14 | end 15 | 16 | def get_pspec(name : String) : LibVips::GParamSpec? 17 | ret = LibVips.vips_object_get_argument(@ohandle, name, out pspec, out _, out _) 18 | ret != 0 ? nil : pspec.value 19 | end 20 | 21 | # Returns a GObject property 22 | def get(name : String) 23 | pspec = get_pspec(name) 24 | raise VipsException.new("Property not found") if pspec.nil? 25 | gtype = pspec.value_type 26 | gv = GValue.new 27 | gv.set_type(gtype) 28 | 29 | get(name, gv) 30 | end 31 | 32 | # Set a GObject property. Value is converted to the property type, if possible. 33 | def set(gtype, name, value) 34 | gv = GValue.new 35 | gv.set_type(gtype) 36 | gv.set(value) 37 | 38 | set(name, gv) 39 | end 40 | 41 | # Set a series of properties using a String 42 | def set(options : String) 43 | LibVips.vips_object_set_from_string(@ohandle, options) == 0 44 | end 45 | 46 | # Get the GType of a GObject property 47 | def get_typeof(name : String) 48 | if pspec = get_pspec(name) 49 | pspec.value_type 50 | else 51 | # need to clear any error, this is horrible 52 | Vips.clear_error 53 | nil 54 | end 55 | end 56 | 57 | # Get the blurb for a GObject property. 58 | def get_blurb(name : String) 59 | pspec = get_pspec(name) 60 | return "" if pspec.nil? 61 | String.new(LibVips.g_param_spec_get_blurb(pspec)) 62 | end 63 | 64 | # Get the description of a GObject. 65 | def get_description 66 | String.new(LibVips.vips_object_get_description(@ohandle)) 67 | end 68 | 69 | # :nodoc: 70 | def to_unsafe 71 | @ohandle 72 | end 73 | end 74 | end 75 | --------------------------------------------------------------------------------