├── .rspec ├── lib ├── blurhash │ └── version.rb └── blurhash.rb ├── spec ├── fixtures │ ├── test.bin │ └── test.png ├── spec_helper.rb └── blurhash_spec.rb ├── ext └── blurhash │ ├── extconf.rb │ ├── encode.h │ └── encode.c ├── bin ├── setup └── console ├── Gemfile ├── CHANGELOG.md ├── .gitignore ├── .travis.yml ├── Rakefile ├── .github └── workflows │ └── gempush.yml ├── Gemfile.lock ├── blurhash.gemspec ├── LICENSE.txt ├── README.md └── CODE_OF_CONDUCT.md /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /lib/blurhash/version.rb: -------------------------------------------------------------------------------- 1 | module Blurhash 2 | VERSION = "0.1.8" 3 | end 4 | -------------------------------------------------------------------------------- /spec/fixtures/test.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gargron/blurhash/HEAD/spec/fixtures/test.bin -------------------------------------------------------------------------------- /spec/fixtures/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gargron/blurhash/HEAD/spec/fixtures/test.png -------------------------------------------------------------------------------- /ext/blurhash/extconf.rb: -------------------------------------------------------------------------------- 1 | require 'mkmf' 2 | 3 | $CFLAGS += ' -std=c99 -lm' 4 | 5 | create_makefile 'blurhash_ext' 6 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 4 | 5 | # Specify your gem's dependencies in blurhash.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | All notable changes to this project will be documented in this file. 5 | 6 | ## [0.1.6] - 2022-02-28 7 | ### Fixed 8 | 9 | - Fix missing `,` in Base 83 character set (noellabo) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | *.bundle 10 | *.so 11 | *.o 12 | *.a 13 | mkmf.log 14 | /ext/blurhash/Makefile 15 | .DS_store 16 | *.gem 17 | 18 | # rspec failure tracking 19 | .rspec_status 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | arch: 2 | - amd64 3 | - arm64 4 | - ppc64le 5 | - s390x 6 | addons: 7 | apt: 8 | packages: 9 | - libmagick++-dev 10 | sudo: false 11 | language: ruby 12 | rvm: 13 | - 2.7.0 14 | - 2.6.5 15 | - 2.6.1 16 | before_install: gem install bundler -v 2.1.4 17 | -------------------------------------------------------------------------------- /ext/blurhash/encode.h: -------------------------------------------------------------------------------- 1 | #ifndef __BLURHASH_ENCODE_H__ 2 | #define __BLURHASH_ENCODE_H__ 3 | 4 | #include 5 | #include 6 | 7 | const char *blurHashForPixels(int xComponents, int yComponents, int width, int height, uint8_t *rgb, size_t bytesPerRow); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | require "rake/extensiontask" 7 | 8 | task :build => :compile 9 | 10 | Rake::ExtensionTask.new("blurhash") do |ext| 11 | ext.name = "blurhash_ext" 12 | end 13 | 14 | task :default => [:clobber, :compile, :spec] 15 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "blurhash" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | require "blurhash" 3 | 4 | RSpec.configure do |config| 5 | # Enable flags like --only-failures and --next-failure 6 | config.example_status_persistence_file_path = ".rspec_status" 7 | 8 | # Disable RSpec exposing methods globally on `Module` and `main` 9 | config.disable_monkey_patching! 10 | 11 | config.expect_with :rspec do |c| 12 | c.syntax = :expect 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /.github/workflows/gempush.yml: -------------------------------------------------------------------------------- 1 | name: Ruby Gem 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - v* 8 | 9 | jobs: 10 | build: 11 | name: Build + Publish 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@master 16 | - uses: ruby/setup-ruby@v1.137.0 17 | with: 18 | ruby-version: '3.0' 19 | - name: Publish to RubyGems 20 | run: | 21 | mkdir -p $HOME/.gem 22 | touch $HOME/.gem/credentials 23 | chmod 0600 $HOME/.gem/credentials 24 | printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials 25 | gem build *.gemspec 26 | gem push *.gem 27 | env: 28 | GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}} 29 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | blurhash (0.1.7) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | diff-lcs (1.4.4) 10 | rake (13.0.3) 11 | rake-compiler (1.1.1) 12 | rake 13 | rspec (3.10.0) 14 | rspec-core (~> 3.10.0) 15 | rspec-expectations (~> 3.10.0) 16 | rspec-mocks (~> 3.10.0) 17 | rspec-core (3.10.1) 18 | rspec-support (~> 3.10.0) 19 | rspec-expectations (3.10.1) 20 | diff-lcs (>= 1.2.0, < 2.0) 21 | rspec-support (~> 3.10.0) 22 | rspec-mocks (3.10.1) 23 | diff-lcs (>= 1.2.0, < 2.0) 24 | rspec-support (~> 3.10.0) 25 | rspec-support (3.10.1) 26 | 27 | PLATFORMS 28 | ruby 29 | 30 | DEPENDENCIES 31 | blurhash! 32 | bundler (~> 2.0) 33 | rake (~> 13.0) 34 | rake-compiler 35 | rspec (~> 3.0) 36 | 37 | BUNDLED WITH 38 | 2.3.23 39 | -------------------------------------------------------------------------------- /spec/blurhash_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Blurhash do 2 | it 'has a version number' do 3 | expect(Blurhash::VERSION).not_to be nil 4 | end 5 | 6 | describe '.encode' do 7 | it 'returns a string' do 8 | pixels = File.read(File.join(__dir__, 'fixtures', 'test.bin')).unpack('C*') 9 | expect(Blurhash.encode(204, 204, pixels)).to eq 'LFE.@D9F01_2%L%MIVD*9Goe-;WB' 10 | end 11 | 12 | it 'raises if pixels array has wrong size' do 13 | expect { Blurhash.encode(204, 204, [0, 1, 2]) }.to raise_error(RuntimeError, 'Pixels array has wrong size') 14 | end 15 | end 16 | 17 | describe '.components' do 18 | it 'returns an array' do 19 | expect(Blurhash.components('LFE.@D9F01_2%L%MIVD*9Goe-;WB')).to eq [4, 3] 20 | end 21 | 22 | it 'returns nil' do 23 | expect(Blurhash.components('foo')).to be_nil 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /blurhash.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('../lib', __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'blurhash/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'blurhash' 9 | spec.version = Blurhash::VERSION 10 | spec.authors = ['Eugen Rochko'] 11 | spec.email = ['eugen@zeonfederated.com'] 12 | 13 | spec.summary = %q{Encode an image as a small string that can saved in the database and used to show a blurred preview before the real image loads} 14 | spec.homepage = 'https://github.com/Gargron/blurhash' 15 | spec.license = 'MIT' 16 | 17 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 18 | f.match(%r{^(test|spec|features)/}) 19 | end 20 | 21 | spec.require_paths = ['lib'] 22 | spec.extensions = ['ext/blurhash/extconf.rb'] 23 | 24 | spec.add_development_dependency 'bundler', '~> 2.0' 25 | spec.add_development_dependency 'rake', '~> 13.0' 26 | spec.add_development_dependency 'rake-compiler' 27 | spec.add_development_dependency 'rspec', '~> 3.0' 28 | end 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Eugen Rochko 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/blurhash.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'blurhash/version' 4 | require 'blurhash_ext' 5 | 6 | module Blurhash 7 | def self.encode(width, height, pixels, x_comp: 4, y_comp: 3) 8 | raise 'Pixels array has wrong size' if pixels.size != width * height * 3 9 | 10 | p = pixels.pack("C#{pixels.size}") 11 | return Unstable.blurHashForPixels(x_comp, y_comp, width, height, p) 12 | end 13 | 14 | def self.components(str) 15 | size_flag = Base83.decode83(str[0]) 16 | y_comp = (size_flag / 9) + 1 17 | x_comp = (size_flag % 9) + 1 18 | 19 | return if str.size != 4 + 2 * x_comp * y_comp 20 | 21 | [x_comp, y_comp] 22 | end 23 | 24 | module Base83 25 | DIGIT_CHARACTERS = %w( 26 | 0 1 2 3 4 5 6 7 8 9 27 | A B C D E F G H I J 28 | K L M N O P Q R S T 29 | U V W X Y Z a b c d 30 | e f g h i j k l m n 31 | o p q r s t u v w x 32 | y z # $ % * + , - . 33 | : ; = ? @ [ ] ^ _ { 34 | | } ~ 35 | ).freeze 36 | 37 | def self.decode83(str) 38 | value = 0 39 | 40 | str.each_char.with_index do |c, i| 41 | digit = DIGIT_CHARACTERS.find_index(c) 42 | value = value * 83 + digit 43 | end 44 | 45 | value 46 | end 47 | end 48 | 49 | private_constant :Unstable 50 | end 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blurhash 2 | 3 | This is a Ruby binding for the Blurhash library. With it you can encode an image as a small string that can be saved in the database, returned in API responses, and displayed as a blurred preview before the real image loads. 4 | 5 | Blurhash is written by [Dag Ågren](https://github.com/DagAgren). 6 | 7 | ## Installation 8 | 9 | Add this line to your application's Gemfile: 10 | 11 | ```ruby 12 | gem 'blurhash' 13 | ``` 14 | 15 | And then execute: 16 | 17 | $ bundle 18 | 19 | Or install it yourself as: 20 | 21 | $ gem install blurhash 22 | 23 | ## Usage 24 | 25 | To generate the blurhash string from an image file, you need to read in the image pixel data yourself, for example with RMagick: 26 | 27 | ```ruby 28 | require 'blurhash' 29 | require 'rmagick' 30 | 31 | image = Magick::ImageList.new('foo.png') 32 | 33 | puts Blurhash.encode(image.columns, image.rows, image.export_pixels) 34 | ``` 35 | 36 | To display the visual component once you have the blurhash string, you need another library in JavaScript, Swift, Kotlin and so on. Fore more information, see [the original blurhash repository](https://github.com/woltapp/blurhash). 37 | 38 | ## Development 39 | 40 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 41 | 42 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 43 | 44 | ## Contributing 45 | 46 | Bug reports and pull requests are welcome on GitHub at https://github.com/Gargron/blurhash. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 47 | 48 | ## License 49 | 50 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 51 | 52 | ## Code of Conduct 53 | 54 | Everyone interacting in the Blurhash project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/Gargron/blurhash/blob/master/CODE_OF_CONDUCT.md). 55 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at eugen@zeonfederated.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /ext/blurhash/encode.c: -------------------------------------------------------------------------------- 1 | #include "encode.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifndef M_PI 8 | #define M_PI 3.14159265358979323846 9 | #endif 10 | 11 | static float *multiplyBasisFunction(int xComponent, int yComponent, int width, int height, uint8_t *rgb, size_t bytesPerRow); 12 | static char *encode_int(int value, int length, char *destination); 13 | 14 | static int linearTosRGB(float value); 15 | static float sRGBToLinear(int value); 16 | static int encodeDC(float r, float g, float b); 17 | static int encodeAC(float r, float g, float b, float maximumValue); 18 | static float signPow(float value, float exp); 19 | 20 | const char *blurHashForPixels(int xComponents, int yComponents, int width, int height, uint8_t *rgb, size_t bytesPerRow) { 21 | static char buffer[2 + 4 + (9 * 9 - 1) * 2 + 1]; 22 | 23 | if(xComponents < 1 || xComponents > 9) return NULL; 24 | if(yComponents < 1 || yComponents > 9) return NULL; 25 | 26 | float factors[yComponents][xComponents][3]; 27 | memset(factors, 0, sizeof(factors)); 28 | 29 | for(int y = 0; y < yComponents; y++) { 30 | for(int x = 0; x < xComponents; x++) { 31 | float *factor = multiplyBasisFunction(x, y, width, height, rgb, bytesPerRow); 32 | factors[y][x][0] = factor[0]; 33 | factors[y][x][1] = factor[1]; 34 | factors[y][x][2] = factor[2]; 35 | } 36 | } 37 | 38 | float *dc = factors[0][0]; 39 | float *ac = dc + 3; 40 | int acCount = xComponents * yComponents - 1; 41 | char *ptr = buffer; 42 | 43 | int sizeFlag = (xComponents - 1) + (yComponents - 1) * 9; 44 | ptr = encode_int(sizeFlag, 1, ptr); 45 | 46 | float maximumValue; 47 | if(acCount > 0) { 48 | float actualMaximumValue = 0; 49 | for(int i = 0; i < acCount * 3; i++) { 50 | actualMaximumValue = fmaxf(fabsf(ac[i]), actualMaximumValue); 51 | } 52 | 53 | int quantisedMaximumValue = fmaxf(0, fminf(82, floorf(actualMaximumValue * 166 - 0.5))); 54 | maximumValue = ((float)quantisedMaximumValue + 1) / 166; 55 | ptr = encode_int(quantisedMaximumValue, 1, ptr); 56 | } else { 57 | maximumValue = 1; 58 | ptr = encode_int(0, 1, ptr); 59 | } 60 | 61 | ptr = encode_int(encodeDC(dc[0], dc[1], dc[2]), 4, ptr); 62 | 63 | for(int i = 0; i < acCount; i++) { 64 | ptr = encode_int(encodeAC(ac[i * 3 + 0], ac[i * 3 + 1], ac[i * 3 + 2], maximumValue), 2, ptr); 65 | } 66 | 67 | *ptr = 0; 68 | 69 | return buffer; 70 | } 71 | 72 | static float *multiplyBasisFunction(int xComponent, int yComponent, int width, int height, uint8_t *rgb, size_t bytesPerRow) { 73 | float r = 0, g = 0, b = 0; 74 | float normalisation = (xComponent == 0 && yComponent == 0) ? 1 : 2; 75 | 76 | for(int y = 0; y < height; y++) { 77 | for(int x = 0; x < width; x++) { 78 | float basis = cosf(M_PI * xComponent * x / width) * cosf(M_PI * yComponent * y / height); 79 | r += basis * sRGBToLinear(rgb[3 * x + 0 + y * bytesPerRow]); 80 | g += basis * sRGBToLinear(rgb[3 * x + 1 + y * bytesPerRow]); 81 | b += basis * sRGBToLinear(rgb[3 * x + 2 + y * bytesPerRow]); 82 | } 83 | } 84 | 85 | float scale = normalisation / (width * height); 86 | 87 | static float result[3]; 88 | result[0] = r * scale; 89 | result[1] = g * scale; 90 | result[2] = b * scale; 91 | 92 | return result; 93 | } 94 | 95 | static int linearTosRGB(float value) { 96 | float v = fmaxf(0, fminf(1, value)); 97 | if(v <= 0.0031308) return v * 12.92 * 255 + 0.5; 98 | else return (1.055 * powf(v, 1 / 2.4) - 0.055) * 255 + 0.5; 99 | } 100 | 101 | static float sRGBToLinear(int value) { 102 | float v = (float)value / 255; 103 | if(v <= 0.04045) return v / 12.92; 104 | else return powf((v + 0.055) / 1.055, 2.4); 105 | } 106 | 107 | static int encodeDC(float r, float g, float b) { 108 | int roundedR = linearTosRGB(r); 109 | int roundedG = linearTosRGB(g); 110 | int roundedB = linearTosRGB(b); 111 | return (roundedR << 16) + (roundedG << 8) + roundedB; 112 | } 113 | 114 | static int encodeAC(float r, float g, float b, float maximumValue) { 115 | int quantR = fmaxf(0, fminf(18, floorf(signPow(r / maximumValue, 0.5) * 9 + 9.5))); 116 | int quantG = fmaxf(0, fminf(18, floorf(signPow(g / maximumValue, 0.5) * 9 + 9.5))); 117 | int quantB = fmaxf(0, fminf(18, floorf(signPow(b / maximumValue, 0.5) * 9 + 9.5))); 118 | 119 | return quantR * 19 * 19 + quantG * 19 + quantB; 120 | } 121 | 122 | static float signPow(float value, float exp) { 123 | return copysignf(powf(fabsf(value), exp), value); 124 | } 125 | 126 | static char characters[83]="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"; 127 | 128 | static char *encode_int(int value, int length, char *destination) { 129 | int divisor = 1; 130 | for(int i = 0; i < length - 1; i++) divisor *= 83; 131 | 132 | for(int i = 0; i < length; i++) { 133 | int digit = (value / divisor) % 83; 134 | divisor /= 83; 135 | *destination++ = characters[digit]; 136 | } 137 | return destination; 138 | } 139 | 140 | VALUE rb_blur_hash_for_pixels(VALUE m, VALUE x_comp, VALUE y_comp, VALUE width, VALUE height, VALUE p) 141 | { 142 | const char * buf = blurHashForPixels(NUM2INT(x_comp), 143 | NUM2INT(y_comp), 144 | NUM2INT(width), 145 | NUM2INT(height), 146 | (uint8_t *)StringValuePtr(p), 147 | NUM2INT(width) * 3); 148 | 149 | return rb_str_new2(buf); 150 | } 151 | 152 | void Init_blurhash_ext() 153 | { 154 | VALUE mBlurhash = rb_define_module("Blurhash"); 155 | VALUE mUnstable = rb_define_module_under(mBlurhash, "Unstable"); 156 | rb_define_singleton_method(mUnstable, "blurHashForPixels", rb_blur_hash_for_pixels, 5); 157 | } 158 | --------------------------------------------------------------------------------