├── .babelrc ├── .gitignore ├── .prettierignore ├── .travis.yml ├── Docs ├── Api.md ├── CONTRIBUTING.MD └── README.md ├── README.md ├── __tests__ ├── __snapshots__ │ └── index.test.js.snap ├── index.test.js └── options.test.js ├── _config.yml ├── images ├── introduction.jpg ├── output.png └── react-impro.png ├── index.js ├── package.json ├── public ├── App.js └── index.html ├── src ├── components │ └── ProcessImage.js ├── index.js ├── jimp.min.js ├── utils │ ├── build.js │ ├── errorMsg.js │ ├── getDimensions.js │ ├── modes.js │ ├── options.js │ ├── propsFactory.js │ └── storage.js ├── validators │ └── props.js └── worker.js ├── webpack ├── webpack.config.dev.js └── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react-app" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .npmignore -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /src/jimp.min.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | notifications: 5 | email: false 6 | script: 7 | - npm run test -------------------------------------------------------------------------------- /Docs/Api.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | ## Component 4 | 5 | **ProcessImage** 6 | 7 | It takes an image, applies desired filters, resizes the image (if) and returns a base64 image. 8 | 9 | ```jsx 10 | this.setState({ src, err })} 14 | /> 15 | ``` 16 | 17 | It does not change the original image. It clones the supplied image and passes it to the filter chain to be processed. 18 | 19 | It uses [`react-progressive-image`](https://github.com/FormidableLabs/react-progressive-image) for placeholder image until the image is processed and displayed. 20 | 21 | By default, the image is processed in a web worker instead of main thread for better performance and responsive UI. You can disable this by setting the value of `disableWebWorker` to `true`. 22 | 23 | ## Props 24 | 25 | ### resize 26 | 27 | It takes image width, height and a resize mode. 28 | 29 | **Type** - `object` 30 | 31 | **Default** - `{ width: AUTO, height: AUTO, mode: 'bilinear' }` 32 | 33 | **resize modes** 34 | 35 | You can pass these values to `mode` 36 | 37 | * [neighbor](https://en.wikipedia.org/wiki/Image_scaling) 38 | * [bilinear](https://en.wikipedia.org/wiki/Image_scaling) 39 | * [bicubic](https://en.wikipedia.org/wiki/Image_scaling) 40 | * [hermite](https://en.wikipedia.org/wiki/Hermite_interpolation) 41 | * bezier 42 | 43 | **Example** - 44 | 45 | ```jsx 46 | 47 | ``` 48 | 49 | ### crop 50 | 51 | It takes x and y coordinates and a width and height. 52 | 53 | **Type** - `object` 54 | 55 | **Default** - `{ width: AUTO, height: AUTO, x: 0, y: 0 }` 56 | 57 | **Example** - 58 | 59 | ```jsx 60 | 61 | ``` 62 | 63 | ### quality 64 | 65 | It takes a number between `1` - `100` for the image quality. 66 | 67 | **Type** - `number` 68 | 69 | **Default** - `AUTO` 70 | 71 | **Example** - 72 | 73 | ```jsx 74 | 75 | ``` 76 | 77 | ### greyscale 78 | 79 | Remove colors from the image 80 | 81 | **Type** - `boolean` 82 | 83 | **Default** - `false` 84 | 85 | **Example** - 86 | 87 | ```jsx 88 | 89 | ``` 90 | 91 | 92 | 93 | ### normalize 94 | 95 | normalize the channels in an image (contrast stretching). For example the images with poor contrast due to glare. 96 | 97 | **Type** - `boolean` 98 | 99 | **Default** - `false` 100 | 101 | **Example** - 102 | 103 | ```jsx 104 | 105 | ``` 106 | 107 | 108 | 109 | ### invert 110 | 111 | invert the image colors 112 | 113 | **Type** - `boolean` 114 | 115 | **Default** - `false` 116 | 117 | **Example** - 118 | 119 | ```jsx 120 | 121 | ``` 122 | 123 | 124 | 125 | ### opaque 126 | 127 | This sets the alpha channel to **opaque** for every pixel. 128 | 129 | **Type** - `boolean` 130 | 131 | **Default** - `false` 132 | 133 | **Example** - 134 | 135 | ```jsx 136 | 137 | ``` 138 | 139 | ### sepia 140 | 141 | creates a reddish brown tone in an image. 142 | 143 | **Type** - `boolean` 144 | 145 | **Default** - `false` 146 | 147 | **Example** - 148 | 149 | ```jsx 150 | 151 | ``` 152 | 153 | 154 | 155 | ### dither565 (similar to ninepatches class in Android) 156 | 157 | mix pixels of two colors 158 | 159 | **Type** - `boolean` 160 | 161 | **Default** - `false` 162 | 163 | **Example** - 164 | 165 | ```jsx 166 | 167 | ``` 168 | 169 | 170 | 171 | ### scale 172 | 173 | scale an image by a factor 174 | 175 | **Type** - `boolean` 176 | 177 | **Default** - `AUTO` 178 | 179 | **Example** - 180 | 181 | ```jsx 182 | 183 | ``` 184 | 185 | ### scaleToFitImage 186 | 187 | scale an image to the largest size that fits inside the given width and height 188 | 189 | **Type** - `object` 190 | 191 | **Default** - `{}` 192 | 193 | **Example** - 194 | 195 | ```jsx 196 | 197 | ``` 198 | 199 | ### flip 200 | 201 | flip the direction of an image 202 | 203 | **Type** - `object` 204 | 205 | **Default** - { horizontal: `false`, vertical: `false` } 206 | 207 | **Example** - 208 | 209 | ```jsx 210 | Optionally, a resize mode can be passed. If `false` is passed as the second parameter, the image width and height will not be resized. 228 | 229 | ### background 230 | 231 | sets the color of any new pixels generated by the image. Will be visible when applying other values such as rotate. 232 | 233 | **Type** - `Hexidecimal rgba`, 234 | 235 | **Default** - `AUTO` 236 | 237 | **Example** - 238 | 239 | ```jsx 240 | 241 | ``` 242 | 243 | ### brightness 244 | 245 | change the brightness level of an image. It takes value from `-1` to `1`. 246 | 247 | **Type** - `number` 248 | 249 | **Example** - 250 | 251 | ```jsx 252 | 253 | ``` 254 | 255 | ### contrast 256 | 257 | change the contrast level of an image. It also takes value from `-1` to `1`. 258 | 259 | **Type** - `number` 260 | 261 | **Example** - 262 | 263 | ```jsx 264 | 265 | ``` 266 | 267 | ### fade 268 | 269 | fades an image by factor `0 - 1`. 270 | 271 | **Type** - `number` 272 | 273 | **Example** - 274 | 275 | ```jsx 276 | 277 | ``` 278 | 279 | #### opacity 280 | 281 | multiply the alpha channel by each pixel by the factor f, 0 - 1. Alternative to fade. 282 | 283 | **Type** - `number` 284 | 285 | **Example** - 286 | 287 | ```jsx 288 | 289 | ``` 290 | 291 | ### blur 292 | 293 | fast blur the image by r pixels. It takes a value from `1` - `100`. 294 | 295 | **Type** - `number` 296 | 297 | **Example** - 298 | 299 | ```jsx 300 | 301 | ``` 302 | 303 | ### posterize 304 | 305 | apply a posterization effect with n level. It takes a value from `1` - `100`. 306 | 307 | **Type** - `number` 308 | 309 | **Example** - 310 | 311 | ```jsx 312 | 313 | ``` 314 | 315 | 316 | 317 | ### cover 318 | 319 | scale the image to the given width and height, some parts of the image may be clipped 320 | 321 | **Type** - `object` 322 | 323 | **Default** - `{}` 324 | 325 | **modes** 326 | 327 | * `horizontal_left` 328 | * `horizontal_center` 329 | * `horizontal_right` 330 | * `vertical_top` 331 | * `vertical_bottom` 332 | * `vertical_middle` 333 | 334 | **Example** - 335 | 336 | ```jsx 337 | 338 | ``` 339 | 340 | ### contain 341 | 342 | scale the image to the given width and height, some parts of the image may be letter boxed 343 | 344 | **Type** - `object` 345 | 346 | **Default** - `{}` 347 | 348 | **modes** 349 | 350 | * `horizontal_left` 351 | * `horizontal_center` 352 | * `horizontal_right` 353 | * `vertical_top` 354 | * `vertical_bottom` 355 | * `vertical_middle` 356 | 357 | **Example** - 358 | 359 | ```jsx 360 | 361 | ``` 362 | 363 | ### colors 364 | 365 | color manipulation 366 | 367 | **Type** - `object` 368 | 369 | **Default** - `{}` 370 | 371 | **color properties** 372 | 373 | ``` 374 | colors = { 375 | lighten: number 376 | brighten: number 377 | darken: number, 378 | desaturate: number, 379 | saturate: number, 380 | greyscale: number, 381 | spin: number, 382 | mix: { 383 | color: string, 384 | amount: number 385 | }, 386 | tint: number, 387 | xor: number, 388 | shade: number, 389 | red: number, 390 | green: number, 391 | blue: number 392 | } 393 | ``` 394 | 395 | > Details given below are taken from [Jimp]() docs. 396 | 397 | * `lighten` - Lighten the color by a given amount, from 0 to 100. Providing 100 will always return white. 398 | * `brighten` - Brighten the color by a given amount, from 0 to 100. 399 | * `darken` - Darken the color by a given amount, from 0 to 100. Providing 100 will always return black. 400 | * `desaturate` - Desaturate the color by a given amount, from 0 to 100. Providing 100 will is the same as calling greyscale. 401 | * `saturate` - Saturate the color by a given amount, from 0 to 100. 402 | * `greyscale` - Completely desaturates a color into greyscale. 403 | * `spin` - spin the color amount from -360 t0 360. 404 | * `mix` - Mixes colors by their RGB component values. Amount is opacity of overlaying color. 405 | * `tint` - Same as applying mix with white color. 406 | * `shade` - Same as applying mix with black color. 407 | * `xor` - Treats the two colors as bitfields and applies an XOR operation to the red, green, and blue components. 408 | * `red` - Modify red component by a given amount. 409 | * `green` - Modify green component by a given amount. 410 | * `blue` - Modify blue component by a given amount. 411 | 412 | ### storage 413 | 414 | localStorage for storing the edited image. 415 | 416 | **Type** - `boolean` 417 | 418 | **Default** - `true` 419 | 420 | **Example** - 421 | 422 | ```jsx 423 | 424 | ``` 425 | 426 | ### disableWebWorker 427 | 428 | disable the web worker and process the image in the main thread (not recommended). 429 | 430 | **Type** - `boolean` 431 | 432 | **Default** - `false` 433 | 434 | **Example** - 435 | 436 | ```jsx 437 | 438 | ``` 439 | 440 | If you disable the web worker, you will need to add [this](https://github.com/nitin42/react-imgpro/blob/master/src/jimp.min.js) file in your `index.html` in order to access `Jimp` instance. 441 | 442 | ### disableRerender 443 | 444 | disable the process image in re-render by options changed (recommended use with worker) 445 | 446 | **Type** - `boolean` 447 | 448 | **Default** - `false` 449 | 450 | **Example** - 451 | 452 | ```jsx 453 | 454 | ``` 455 | 456 | ### customCdn 457 | 458 | support you can add custom cdn for jimp 459 | 460 | **Type** - `string` 461 | 462 | **Example** - 463 | 464 | ```jsx 465 | 466 | ``` 467 | 468 | ### onProcessFinish 469 | 470 | **Type** - `function` 471 | 472 | **Example** - 473 | 474 | a callback on process finished 475 | 476 | ```jsx 477 | { 480 | this.setState({ 481 | isProcessing: false 482 | }); 483 | }} 484 | /> 485 | ``` 486 | 487 | ### getImageRef 488 | 489 | **Type** - `function` 490 | 491 | **Example** - 492 | 493 | get image ref 494 | 495 | ```jsx 496 | this.image=image} 499 | /> 500 | ``` 501 | -------------------------------------------------------------------------------- /Docs/CONTRIBUTING.MD: -------------------------------------------------------------------------------- 1 | # Contributing guide 2 | 3 | I'm excited to have you helping out. Thank you so much for your time 😄 4 | 5 | ### Contributing 6 | 7 | #### Understanding the codebase 8 | `react-imgpro` uses [Jimp](https://github.com/oliver-moran/jimp) for processing the images but will later extends it support for third party OpenGL libs. So before you start working on a PR, take a look at Jimp docs and it's features. 9 | 10 | > [This](https://github.com/nitin42/react-imgpro/tree/with-comments) branch includes comments for every function and module. This may help you in understanding the codebase more easily. 11 | 12 | #### Setting up the environment 13 | 14 | Considering you've forked and cloned the repo on your system, switch to the directory and install the dependencies. 15 | 16 | ``` 17 | cd react-imgpro 18 | npm install 19 | ``` 20 | 21 | #### Submitting pull requests 22 | 23 | * Create a new branch for the new feature: git checkout -b new-feature 24 | * Make your changes. 25 | * Test everything with `npm run test`. 26 | * Run `npm run lint` to check the syntax errors. 27 | * Commit your changes: git commit -m 'Added some new feature' 28 | * Push to the branch: git push origin new-feature 29 | * Submit a pull request with full remarks documenting your changes. 30 | 31 | 32 | That's it! I am excited to see your pull request. 33 | -------------------------------------------------------------------------------- /Docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## Table of content 4 | 5 | * [Introduction](https://github.com/nitin42/react-imgpro#introduction) 6 | * [Motivation](https://github.com/nitin42/react-imgpro#motivation) 7 | * [Demo](https://github.com/nitin42/react-imgpro#demo) 8 | * [Install](https://github.com/nitin42/react-imgpro#install) 9 | * [Usage](https://github.com/nitin42/react-imgpro#usage) 10 | * [API Reference](./Api.md) 11 | * [ProcessImage component](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#component) 12 | * [props](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#props) 13 | * [resize](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#resize) 14 | * [crop](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#crop) 15 | * [quality](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#quality) 16 | * [greyscale](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#greyscale) 17 | * [normalize](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#normalize) 18 | * [invert](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#invert) 19 | * [opaque](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#opaque) 20 | * [sepia](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#sepia) 21 | * [dither565](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#dither565) 22 | * [scale](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#scale) 23 | * [scaleToFit](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#scaletofitimage) 24 | * [flip](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#flip) 25 | * [rotate](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#rotate) 26 | * [brightness](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#brightness) 27 | * [contrast](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#contrast) 28 | * [fade](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#fade) 29 | * [opacity](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#opacity) 30 | * [blur](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#blur) 31 | * [posterize](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#posterize) 32 | * [cover](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#cover) 33 | * [contain](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#contain) 34 | * [colors](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#colors) 35 | * [storage](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#storage) 36 | * [disableWebWorker](https://github.com/nitin42/react-imgpro/blob/master/Docs/Api.md#disablewebworker) 37 | * [Contributing](./CONTRIBUTING.MD) 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This project is no longer maintained.** 2 | 3 | # react-imgpro 4 | 5 | [![Build Status](https://travis-ci.org/nitin42/react-imgpro.svg?branch=master)](https://travis-ci.org/nitin42/react-imgpro) 6 | ![status](https://img.shields.io/badge/version-1.3.14-brightgreen.svg) 7 | ![status](https://img.shields.io/badge/size-13.1KB-brightgreen.svg) 8 | ![status](https://img.shields.io/badge/status-stable-brightgreen.svg) 9 | ![yarn](https://img.shields.io/badge/yarn-1.9.4-blue.svg) 10 | 11 | > Image Processing Component for React 12 | 13 |

14 | 15 |

16 | 17 | ## Introduction 18 | 19 | `react-imgpro` is a image processing component for React. This component process an image with filters supplied as props and returns a [base64](https://en.wikipedia.org/wiki/Base64) image. 20 | 21 | **Example** 22 | 23 | ```jsx 24 | 25 | const mix = { 26 | color: 'mistyrose', 27 | amount: 10 28 | } 29 | 30 | class App extends React.Component { 31 | state = { src: '', err: null } 32 | render() { 33 | return ( 34 | this.setState({ src, err, })} 41 | /> 42 | ) 43 | } 44 | } 45 | ``` 46 | 47 |

48 | 49 |

50 | 51 | ## Motivation 52 | 53 |

54 | 55 |

56 | 57 | I was working on a project last month which involved a lot of image processing and I'd to rely on third party libraries. But before using them directly, I'd to learn different concepts in gl (shaders) and then try to implement them in React. The difficult part was not learning but it was the verbosity, boilerplate code and redundancy introduced by the libraries in the codebase. It was getting difficult to organise all the things 😞 58 | 59 | So I wanted a layer of abstraction which would make it easy to manipulate the colors of the image, applying filters and gl shaders efficiently with ease. And React's component based model was perfect for hiding all the implementation details in a component 😄 60 | 61 | ## Demo 62 | 63 |

64 | 65 |

66 | 67 | ## Install 68 | 69 | ``` 70 | npm install react-imgpro 71 | ``` 72 | 73 | This also depends on `react` so make sure you've installed it. 74 | 75 | OR 76 | 77 | The UMD build is also available via [jsDelivr](https://www.jsdelivr.com). 78 | 79 | ``` 80 | 81 | 82 | ``` 83 | 84 | ## Usage 85 | 86 | ```jsx 87 | import React from 'react'; 88 | import ProcessImage from 'react-imgpro'; 89 | 90 | class App extends React.Component { 91 | state = { 92 | src: '', 93 | err: null 94 | } 95 | 96 | render() { 97 | return ( 98 | this.setState({ src, err})} 108 | /> 109 | ) 110 | } 111 | } 112 | 113 | ``` 114 | 115 | ## Documentation 116 | 117 | See the detailed documentation [here](./Docs). 118 | 119 | ## SSR support ? 120 | 121 | Yes, `react-imgpro` supports SSR. 122 | 123 | ## Contributing 124 | 125 | [Contributing guide](https://github.com/nitin42/react-imgpro/blob/master/Docs/CONTRIBUTING.MD). 126 | 127 | ## Extra resources 128 | 129 | If you want to use blenders, plugins and perform event based calculations, try [CamanJS](http://camanjs.com/). 130 | 131 | ## License 132 | 133 | MIT 134 | 135 | Sponsor 136 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`getDimension should fallback to default 1`] = `300`; 4 | 5 | exports[`getDimension should return the image height when attribute passed is 'height' 1`] = `300`; 6 | 7 | exports[`getDimension should return the image width when attribute passed is 'width' 1`] = `400`; 8 | -------------------------------------------------------------------------------- /__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import renderer from 'react-test-renderer'; 2 | import getSize from '../src/utils/getDimensions'; 3 | 4 | describe('getDimension', () => { 5 | const inp = { resize: { height: 300, width: 400 }}; 6 | const def = 300; 7 | 8 | test('should return the image height when attribute passed is \'height\' ', () => { 9 | const height = getSize(inp, def, 'height'); 10 | 11 | expect(height).toMatchSnapshot(); 12 | }) 13 | 14 | test('should return the image width when attribute passed is \'width\' ', () => { 15 | const width = getSize(inp, def, 'width'); 16 | 17 | expect(width).toMatchSnapshot(); 18 | }); 19 | 20 | test('should fallback to default', () => { 21 | const height = getSize({}, def, 'height'); 22 | 23 | expect(height).toMatchSnapshot(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /__tests__/options.test.js: -------------------------------------------------------------------------------- 1 | import renderer from 'react-test-renderer'; 2 | import Jimp from 'jimp'; 3 | import processImage from '../src/utils/options'; 4 | 5 | describe('image options', () => { 6 | const image = 'http://365.unsplash.com/assets/paul-jarvis-9530891001e7f4ccfcef9f3d7a2afecd.jpg'; 7 | let output = null; 8 | 9 | test('sanity check', () => { 10 | Jimp.read(image).then(image => { 11 | const output = processImage(image, { resize: { height: 300 } }, Jimp) 12 | expect(output.bitmap.height).toEqual(300); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /images/introduction.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitin42/react-imgpro/bf525f42c8ff4fa2b64fe043a1f92f05ed125a1b/images/introduction.jpg -------------------------------------------------------------------------------- /images/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitin42/react-imgpro/bf525f42c8ff4fa2b64fe043a1f92f05ed125a1b/images/output.png -------------------------------------------------------------------------------- /images/react-impro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitin42/react-imgpro/bf525f42c8ff4fa2b64fe043a1f92f05ed125a1b/images/react-impro.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./build/main.js'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-imgpro", 3 | "version": "1.4.1", 4 | "description": "Image processing component for React", 5 | "main": "index.js", 6 | "files": [ 7 | "build", 8 | "index.js" 9 | ], 10 | "keywords": [ 11 | "react", 12 | "image processing", 13 | "image", 14 | "jimp", 15 | "sepia", 16 | "colors", 17 | "invert", 18 | "image component" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/nitin42/react-imgpro" 23 | }, 24 | "author": "Nitin Tulswani", 25 | "license": "MIT", 26 | "dependencies": { 27 | "browser-image-size": "^1.1.0", 28 | "lint-staged": "^8.0.4", 29 | "react-progressive-image": "^0.3.0", 30 | "webworkify-webpack": "^2.0.5", 31 | "window-or-global": "^1.0.1" 32 | }, 33 | "peerDependencies": { 34 | "react": "^16.0.0" 35 | }, 36 | "resolutions": { 37 | "babel-core": "7.0.0-bridge.0" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "7.0.0-beta.46", 41 | "@babel/runtime": "7.0.0-beta.46", 42 | "babel-eslint": "^7.2.3", 43 | "babel-jest": "^23.4.2", 44 | "babel-loader": "^8.0.0", 45 | "babel-plugin-macros": "2.2.1", 46 | "babel-plugin-transform-dynamic-import": "2.0.0", 47 | "babel-plugin-transform-react-remove-prop-types": "0.4.13", 48 | "babel-preset-react-app": "^4.0.0-next.a671462c", 49 | "babili-webpack-plugin": "^0.1.2", 50 | "husky": "^0.14.3", 51 | "jest": "^23.5.0", 52 | "jimp": "^0.5.6", 53 | "prettier": "^1.11.1", 54 | "prop-types": "^15.5.10", 55 | "react": "^16.0.0", 56 | "react-dom": "^16.0.0", 57 | "react-test-renderer": "^16.0.0", 58 | "uglifyjs-webpack-plugin": "^1.3.0", 59 | "webpack": "^4.17.2", 60 | "webpack-cli": "^3.1.0", 61 | "webpack-dev-server": "^3.1.8", 62 | "worker-loader": "^2.0.0", 63 | "workerize-loader": "^1.0.4" 64 | }, 65 | "scripts": { 66 | "start": "NODE_ENV=production ./node_modules/.bin/webpack-dev-server --content-base ./public --config ./webpack/webpack.config.dev.js", 67 | "prebuild": "rm -rf ./build", 68 | "build": "NODE_ENV=production ./node_modules/.bin/webpack --config ./webpack/webpack.config.js --progress", 69 | "test": "jest", 70 | "test:watch": "jest --watch", 71 | "format": "prettier --write --single-quote \"src/**/*.js\"", 72 | "precommit": "lint-staged" 73 | }, 74 | "lint-staged": { 75 | "*.js": [ 76 | "prettier --write --single-quote \"src/**/*.js\"", 77 | "git add" 78 | ] 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /public/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { render } from 'react-dom'; 3 | 4 | import ProcessImage from '../src'; 5 | 6 | const src = 'http://orscxqn8h.bkt.clouddn.com/18-3-3/943334.jpg'; 7 | 8 | class App extends Component { 9 | state = { 10 | src: '', 11 | err: '', 12 | sepia: true, 13 | mixAmount: 10, 14 | isProcessing: false 15 | }; 16 | 17 | render() { 18 | return ( 19 |
20 | { 26 | this.setState({ 27 | isProcessing: false 28 | }); 29 | }} 30 | colors={{ 31 | mix: { 32 | color: 'mistyrose', 33 | amount: this.state.mixAmount 34 | } 35 | }} 36 | getImageRef={element => (this.image = element)} 37 | /> 38 | 49 | 50 | 61 | 68 |
69 | ); 70 | } 71 | } 72 | 73 | render(, document.getElementById('root')); 74 | 75 | /** 76 | * processImage prop (validation) 77 | * 78 | * 79 | */ 80 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | React App 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/ProcessImage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ProgressiveImage from 'react-progressive-image'; 3 | import size from 'browser-image-size'; 4 | import work from 'webworkify-webpack'; 5 | import root from 'window-or-global'; 6 | import { filterPropsToListen, getImageProps } from '../utils/propsFactory'; 7 | import getSize from '../utils/getDimensions'; 8 | import { noJimpInstance, webWorkerInfo } from '../utils/errorMsg'; 9 | import { setItem, getItem, removeItem } from '../utils/storage'; 10 | import ROOT from '../utils/build'; 11 | import MainPropTypes from '../validators/props'; 12 | import worker from 'workerize-loader?inline!../worker'; 13 | import processImage from '../utils/options'; 14 | 15 | class ProcessImage extends Component { 16 | static propTypes = MainPropTypes; 17 | 18 | static defaultProps = { 19 | storage: true, 20 | greyscale: false, 21 | normalize: false, 22 | invert: false, 23 | opaque: false, 24 | sepia: false, 25 | dither565: false, 26 | disableWebWorker: false 27 | }; 28 | 29 | state = { 30 | src: '', 31 | err: '', 32 | height: null, 33 | width: null 34 | }; 35 | 36 | componentWillMount = () => { 37 | this.checkStorageSupport(); 38 | 39 | if (typeof Worker !== 'undefined' && !this.props.disableWebWorker) { 40 | this.worker = worker(); 41 | } 42 | }; 43 | 44 | componentDidMount = () => { 45 | this.getOriginalImageSize(this.props); 46 | 47 | this.processInMainThreadOrInWebWorker( 48 | this.worker, 49 | this.props, 50 | this.myStorage 51 | ); 52 | }; 53 | 54 | componentDidUpdate = () => { 55 | if (this.props.image && !this.props.disableRerender) { 56 | this.processInMainThreadOrInWebWorker( 57 | this.worker, 58 | this.props, 59 | this.myStorage 60 | ); 61 | } 62 | }; 63 | 64 | componentWillUnmount = () => { 65 | this.worker !== null ? this.worker.terminate() : null; 66 | 67 | removeItem('placeholder', this.myStorage); 68 | }; 69 | 70 | checkStorageSupport = () => { 71 | if (typeof Storage !== 'undefined' && this.props.storage) { 72 | return (this.myStorage = root.localStorage); 73 | } else if (!this.props.storage && typeof Storage !== 'undefined') { 74 | this.clearStorage(); 75 | return (this.myStorage = null); 76 | } 77 | 78 | return (this.myStorage = null); 79 | }; 80 | 81 | passPropsToParent = (props, src, err) => 82 | props.processedImage !== undefined ? props.processedImage(src, err) : null; 83 | 84 | processInMainThreadOrInWebWorker = (worker, props, storageReference) => { 85 | if (typeof Worker !== 'undefined' && !props.disableWebWorker) { 86 | return this.processInWebWorker(worker, props, storageReference); 87 | } else { 88 | if (ROOT !== undefined && props.disableWebWorker) { 89 | console.info(webWorkerInfo); 90 | return this.processInMainThread(props); 91 | } else { 92 | return console.error(noJimpInstance); 93 | } 94 | } 95 | }; 96 | 97 | clearStorage = () => root.localStorage.removeItem('placeholder'); 98 | 99 | getOriginalImageSize = props => { 100 | size(props.image) 101 | .then(size => this.setState({ height: size.height, width: size.width })) 102 | .catch(() => { 103 | if (props.processedImage) { 104 | const err = new Error('Unable to get original size of image'); 105 | props.processedImage('', err); 106 | } 107 | }); 108 | }; 109 | 110 | getDefaultImageSize = props => { 111 | const { height, width } = this.state; 112 | 113 | return { 114 | height: getSize(props, height, 'height'), 115 | width: getSize(props, width, 'width') 116 | }; 117 | }; 118 | 119 | myStorage = null; 120 | 121 | processInMainThread = props => { 122 | ROOT.read(props.image) 123 | .then(image => { 124 | processImage(image, props, ROOT).getBase64(ROOT.AUTO, (err, src) => { 125 | if (this.state.src !== src || this.state.err !== err) { 126 | this.setState({ src, err }); 127 | this.passPropsToParent(props, src, err); 128 | if (typeof props.onProcessFinish === 'function') { 129 | props.onProcessFinish(); 130 | } 131 | } 132 | }); 133 | }) 134 | .catch(err => { 135 | this.passPropsToParent(props, '', result.err); 136 | if (typeof props.onProcessFinish === 'function') { 137 | props.onProcessFinish(); 138 | } 139 | return; 140 | }); 141 | }; 142 | 143 | processInWebWorker = async (worker, props, storageReference) => { 144 | if (worker !== null) { 145 | const result = await worker 146 | .process({ 147 | props: filterPropsToListen(props), 148 | image: props.image, 149 | customCdn: props.customCdn 150 | }) 151 | .catch(err => ({ err })); 152 | 153 | if (typeof result.src === 'undefined' && result.err) { 154 | this.passPropsToParent(props, '', result.err); 155 | if (typeof props.onProcessFinish === 'function') { 156 | props.onProcessFinish(); 157 | } 158 | return; 159 | } 160 | 161 | if (result.src !== this.state.src || result.err !== this.state.err) { 162 | this.setState({ src: result.src, err: result.err }); 163 | setItem('placeholder', result.src, storageReference); 164 | this.passPropsToParent(props, result.src, result.err); 165 | if (typeof props.onProcessFinish === 'function') { 166 | props.onProcessFinish(); 167 | } 168 | } 169 | } 170 | }; 171 | 172 | worker = null; 173 | 174 | processedImage = (image, restProps, style, getImageRef) => ( 175 | 176 | ); 177 | 178 | placeholderImage = image => 179 | getItem('placeholder', this.myStorage) === null 180 | ? image 181 | : getItem('placeholder', this.myStorage); 182 | 183 | showImage = (img, props, restProps) => ( 184 | 188 | {image => 189 | this.processedImage( 190 | image, 191 | restProps, 192 | this.getDefaultImageSize(props), 193 | props.getImageRef 194 | ) 195 | } 196 | 197 | ); 198 | 199 | render() { 200 | const { src } = this.state; 201 | const restProps = getImageProps(this.props); 202 | return this.showImage(src, this.props, restProps); 203 | } 204 | } 205 | 206 | export default ProcessImage; 207 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import ProcessImage from './components/ProcessImage'; 2 | 3 | export default ProcessImage; 4 | -------------------------------------------------------------------------------- /src/utils/build.js: -------------------------------------------------------------------------------- 1 | import root from 'window-or-global'; 2 | 3 | let ROOT = 4 | root.Jimp !== undefined && typeof root.Jimp === 'function' 5 | ? root.Jimp 6 | : undefined; 7 | 8 | export default ROOT; 9 | -------------------------------------------------------------------------------- /src/utils/errorMsg.js: -------------------------------------------------------------------------------- 1 | const noJimpInstance = `Browser build for Jimp not found. Place this in your index.html file and restart the server - 2 | 3 | `; 4 | 5 | const webWorkerInfo = 6 | 'For better performance, set disableWebWorker to false. This will keep your UI responsive as the image will be processed in a web worker.'; 7 | 8 | export { noJimpInstance, webWorkerInfo }; 9 | -------------------------------------------------------------------------------- /src/utils/getDimensions.js: -------------------------------------------------------------------------------- 1 | let getSize = (props, original, attr) => { 2 | if (props.resize !== undefined) return props.resize[attr]; 3 | if (props.crop !== undefined) return props.crop[attr]; 4 | if (props.contain !== undefined) return props.contain[attr]; 5 | if (props.cover !== undefined) return props.cover[attr]; 6 | if (props.scaleToFit !== undefined) return props.scaleToFit[attr]; 7 | if (props.style !== undefined) return props.style[attr]; 8 | if (props[attr] !== undefined) return parseInt(props[attr], 10); 9 | 10 | return original; 11 | }; 12 | 13 | export default getSize; 14 | -------------------------------------------------------------------------------- /src/utils/modes.js: -------------------------------------------------------------------------------- 1 | const RESIZE_MODES = { 2 | neighbor: 'RESIZE_NEAREST_NEIGHBOR', 3 | bilinear: 'RESIZE_BILINEAR', 4 | bicubic: 'RESIZE_BICUBIC', 5 | hermite: 'RESIZE_HERMITE', 6 | bezier: 'RESIZE_BEZIER' 7 | }; 8 | 9 | const ALIGN_MODES = { 10 | horizontal_left: 'HORIZONTAL_ALIGN_LEFT', 11 | horizontal_center: 'HORIZONTAL_ALIGN_CENTER', 12 | horizontal_right: 'HORIZONTAL_ALIGN_RIGHT', 13 | vertical_top: 'VERTICAL_ALIGN_TOP', 14 | vertical_middle: 'VERTICAL_ALIGN_MIDDLE', 15 | vertical_bottom: 'VERTICAL_ALIGN_BOTTOM' 16 | }; 17 | 18 | module.exports = { 19 | ALIGN_MODES, 20 | RESIZE_MODES 21 | }; 22 | -------------------------------------------------------------------------------- /src/utils/options.js: -------------------------------------------------------------------------------- 1 | const { ALIGN_MODES, RESIZE_MODES } = require('./modes'); 2 | 3 | function processImage(image, props, ROOT) { 4 | const { 5 | resize, 6 | quality, 7 | greyscale, 8 | contain, 9 | cover, 10 | normalize, 11 | invert, 12 | opaque, 13 | sepia, 14 | dither565, 15 | scale, 16 | scaleToFit, 17 | flip, 18 | rotate, 19 | brightness, 20 | contrast, 21 | fade, 22 | opacity, 23 | blur, 24 | posterize, 25 | crop, 26 | background 27 | } = props; 28 | 29 | function MODE(algorithm) { 30 | return ROOT[algorithm]; 31 | } 32 | 33 | function getMode(prop, modes) { 34 | return prop !== undefined ? modes[prop.mode] : null; 35 | } 36 | 37 | function setMode(prop, modes, autoMode) { 38 | return MODE(getMode(prop, modes)) || autoMode; 39 | } 40 | 41 | const AUTOMEASURE = ROOT.AUTO; 42 | 43 | const setter = (value, fallbackTo) => 44 | value !== undefined ? value : fallbackTo; 45 | 46 | image.__proto__.pass = function(image) { 47 | return image; 48 | }; 49 | 50 | function setFilterOrForwardTheImage(filter, img, fn) { 51 | return filter ? img[fn]() : image.pass(img); 52 | } 53 | 54 | function scaleImageWithoutMode(prop, img, scaleMode) { 55 | return prop !== undefined && Object.keys(prop).length > 0 56 | ? img[scaleMode]( 57 | prop.width, 58 | prop.height, 59 | setMode(prop, ALIGN_MODES, ROOT.HORIZONTAL_ALIGN_CENTER) 60 | ) 61 | : image.pass(image); 62 | } 63 | 64 | function changeImageAppearence(prop, img, filter) { 65 | return prop !== undefined ? img[filter](prop) : image.pass(img); 66 | } 67 | 68 | function colorManipulation(props) { 69 | const setConfig = []; 70 | 71 | if (props.colors !== undefined) { 72 | Object.keys(props.colors).forEach(option => { 73 | const setAmountWithColor = ['mix', 'xor']; 74 | 75 | if (setAmountWithColor.includes(option)) { 76 | const schemaOne = { 77 | apply: option, 78 | params: [props.colors[option].color, props.colors[option].amount] 79 | }; 80 | 81 | setConfig.push(schemaOne); 82 | } 83 | 84 | const schemaTwo = { 85 | apply: option, 86 | params: [props.colors[option]] 87 | }; 88 | 89 | setConfig.push(schemaTwo); 90 | }); 91 | return setConfig; 92 | } 93 | 94 | return []; 95 | } 96 | 97 | image.__proto__.resizeAnImage = function(image, resize) { 98 | return resize !== undefined && Object.keys(resize).length > 0 99 | ? image.resize( 100 | setter(resize.width, AUTOMEASURE), 101 | setter(resize.height, AUTOMEASURE), 102 | setMode(resize, RESIZE_MODES, ROOT.RESIZE_BILINEAR) 103 | ) 104 | : image.pass(image); 105 | }; 106 | 107 | image.__proto__.cropImage = function(image, crop) { 108 | return crop !== undefined 109 | ? image.crop( 110 | setter(crop.x, AUTOMEASURE), 111 | setter(crop.y, AUTOMEASURE), 112 | setter(crop.width, 0), 113 | setter(crop.height, 0) 114 | ) 115 | : image.pass(image); 116 | }; 117 | 118 | image.__proto__.changeImageQuality = function(image, quality) { 119 | return changeImageAppearence(quality, image, 'quality'); 120 | }; 121 | 122 | image.__proto__.applyGreyscale = function(image, greyscale) { 123 | return setFilterOrForwardTheImage(greyscale, image, 'greyscale'); 124 | }; 125 | 126 | image.__proto__.normalizeImage = function(image, normalize) { 127 | return setFilterOrForwardTheImage(normalize, image, 'normalize'); 128 | }; 129 | 130 | image.__proto__.invertImage = function(image, invert) { 131 | return setFilterOrForwardTheImage(invert, image, 'invert'); 132 | }; 133 | 134 | image.__proto__.opaqueImage = function(image, opaque) { 135 | return setFilterOrForwardTheImage(opaque, image, 'opaque'); 136 | }; 137 | 138 | image.__proto__.sepiaFilter = function(image, sepia) { 139 | return setFilterOrForwardTheImage(sepia, image, 'sepia'); 140 | }; 141 | 142 | image.__proto__.ditherFilter = function(image, dither565) { 143 | return setFilterOrForwardTheImage(dither565, image, 'dither565'); 144 | }; 145 | 146 | image.__proto__.scaleImage = function(image, scale) { 147 | return changeImageAppearence(scale, image, 'scale'); 148 | }; 149 | 150 | image.__proto__.scaleToFitImage = function(image, scaleToFit) { 151 | return scaleToFit !== undefined 152 | ? image.scaleToFit( 153 | setter(scaleToFit.width, AUTOMEASURE), 154 | setter(scaleToFit.height, AUTOMEASURE) 155 | ) 156 | : image.pass(image); 157 | }; 158 | 159 | image.__proto__.flipImage = function(image, flip) { 160 | return flip !== undefined 161 | ? image.flip(setter(flip.horizontal, false), setter(flip.vertical, false)) 162 | : image.pass(image); 163 | }; 164 | 165 | image.__proto__.rotateImage = function(image, rotate) { 166 | return rotate !== undefined 167 | ? image.rotate( 168 | setter(rotate.degree, 0), 169 | setMode(rotate, RESIZE_MODES, false) 170 | ) 171 | : image.pass(image); 172 | }; 173 | 174 | image.__proto__.applyBackground = function(image, background) { 175 | return background !== undefined 176 | ? image.background(background) 177 | : image.pass(image); 178 | }; 179 | 180 | image.__proto__.changeBrightness = function(image, brightness) { 181 | return changeImageAppearence(brightness, image, 'brightness'); 182 | }; 183 | 184 | image.__proto__.changeContrast = function(image, contrast) { 185 | return changeImageAppearence(contrast, image, 'contrast'); 186 | }; 187 | 188 | image.__proto__.fadeImage = function(image, fade) { 189 | return changeImageAppearence(fade, image, 'fade'); 190 | }; 191 | 192 | image.__proto__.changeOpacity = function(image, opacity) { 193 | return changeImageAppearence(opacity, image, 'opacity'); 194 | }; 195 | 196 | image.__proto__.blurImage = function(image, blur) { 197 | return changeImageAppearence(blur, image, 'blur'); 198 | }; 199 | 200 | image.__proto__.posterizeImage = function(image, posterize) { 201 | return changeImageAppearence(posterize, image, 'posterize'); 202 | }; 203 | 204 | image.__proto__.containImage = function(image, contain) { 205 | return scaleImageWithoutMode(contain, image, 'contain'); 206 | }; 207 | 208 | image.__proto__.coverImage = function(image, cover) { 209 | return scaleImageWithoutMode(cover, image, 'cover'); 210 | }; 211 | 212 | return image 213 | .clone() 214 | .applyBackground(image, background) 215 | .resizeAnImage(image, resize) 216 | .changeImageQuality(image, quality) 217 | .applyGreyscale(image, greyscale) 218 | .normalizeImage(image, normalize) 219 | .invertImage(image, invert) 220 | .opaqueImage(image, opaque) 221 | .sepiaFilter(image, sepia) 222 | .ditherFilter(image, dither565) 223 | .scaleImage(image, scale) 224 | .scaleToFitImage(image, scaleToFit) 225 | .flipImage(image, flip) 226 | .rotateImage(image, rotate) 227 | .cropImage(image, crop) 228 | .changeBrightness(image, brightness) 229 | .changeContrast(image, contrast) 230 | .fadeImage(image, fade) 231 | .changeOpacity(image, opacity) 232 | .blurImage(image, blur) 233 | .posterizeImage(image, posterize) 234 | .coverImage(image, cover) 235 | .color(colorManipulation(props)) 236 | .containImage(image, contain); 237 | } 238 | 239 | export default processImage; 240 | -------------------------------------------------------------------------------- /src/utils/propsFactory.js: -------------------------------------------------------------------------------- 1 | const filterPropsToListen = props => { 2 | const { 3 | resize, 4 | quality, 5 | greyscale, 6 | contain, 7 | cover, 8 | normalize, 9 | invert, 10 | opaque, 11 | sepia, 12 | dither565, 13 | scale, 14 | crop, 15 | scaleToFit, 16 | flip, 17 | rotate, 18 | brightness, 19 | contrast, 20 | fade, 21 | opacity, 22 | blur, 23 | posterize, 24 | colors, 25 | background 26 | } = props; 27 | 28 | return { 29 | resize, 30 | quality, 31 | greyscale, 32 | contain, 33 | cover, 34 | crop, 35 | normalize, 36 | invert, 37 | opaque, 38 | sepia, 39 | dither565, 40 | scale, 41 | scaleToFit, 42 | flip, 43 | rotate, 44 | brightness, 45 | contrast, 46 | fade, 47 | opacity, 48 | blur, 49 | posterize, 50 | colors, 51 | background 52 | }; 53 | }; 54 | 55 | const getImageProps = props => { 56 | const { 57 | image, 58 | resize, 59 | quality, 60 | greyscale, 61 | contain, 62 | cover, 63 | normalize, 64 | invert, 65 | opaque, 66 | sepia, 67 | dither565, 68 | scale, 69 | scaleToFit, 70 | flip, 71 | rotate, 72 | crop, 73 | brightness, 74 | contrast, 75 | fade, 76 | opacity, 77 | blur, 78 | posterize, 79 | colors, 80 | placeholder, 81 | processedImage, 82 | storage, 83 | disableWebWorker, 84 | disableRerender, 85 | customCdn, 86 | onProcessFinish, 87 | background, 88 | getImageRef, 89 | ...rest 90 | } = props; 91 | 92 | return rest; 93 | }; 94 | 95 | export { filterPropsToListen, getImageProps }; 96 | -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | const setItem = (key, value, store) => 2 | store !== null ? store.setItem(key, value) : null; 3 | 4 | const getItem = (key, store) => (store !== null ? store.getItem(key) : null); 5 | 6 | const removeItem = (key, store) => 7 | store !== null ? store.removeItem(key) : null; 8 | 9 | export { getItem, removeItem, setItem }; 10 | -------------------------------------------------------------------------------- /src/validators/props.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | const resizePropType = PropTypes.shape({ 4 | width: PropTypes.number, 5 | height: PropTypes.number, 6 | mode: PropTypes.string 7 | }); 8 | 9 | const containPropType = PropTypes.shape({ 10 | width: PropTypes.number, 11 | height: PropTypes.number, 12 | mode: PropTypes.string 13 | }); 14 | 15 | const coverPropType = PropTypes.shape({ 16 | width: PropTypes.number, 17 | height: PropTypes.number, 18 | mode: PropTypes.string 19 | }); 20 | 21 | const scaleToFitPropType = PropTypes.shape({ 22 | width: PropTypes.number, 23 | height: PropTypes.number 24 | }); 25 | 26 | const flipPropType = PropTypes.shape({ 27 | horizontal: PropTypes.bool, 28 | vertical: PropTypes.bool 29 | }); 30 | 31 | const rotatePropType = PropTypes.shape({ 32 | degree: PropTypes.number, 33 | mode: PropTypes.string 34 | }); 35 | 36 | const cropPropType = PropTypes.shape({ 37 | x: PropTypes.number, 38 | y: PropTypes.number, 39 | w: PropTypes.number, 40 | h: PropTypes.number 41 | }); 42 | 43 | const mixPropType = PropTypes.shape({ 44 | color: PropTypes.string, 45 | amount: PropTypes.number 46 | }); 47 | 48 | const xorPropType = PropTypes.shape({ 49 | color: PropTypes.string, 50 | amount: PropTypes.number 51 | }); 52 | 53 | const colorsPropType = PropTypes.shape({ 54 | lighten: PropTypes.number, 55 | brighten: PropTypes.number, 56 | darken: PropTypes.number, 57 | desaturate: PropTypes.number, 58 | saturate: PropTypes.number, 59 | greyscale: PropTypes.number, 60 | spin: PropTypes.number, 61 | mix: mixPropType, 62 | tint: PropTypes.number, 63 | shade: PropTypes.number, 64 | xor: xorPropType, 65 | red: PropTypes.number, 66 | green: PropTypes.number, 67 | blue: PropTypes.number 68 | }); 69 | 70 | const MainPropTypes = { 71 | blur: PropTypes.number, 72 | brightness: PropTypes.number, 73 | contain: containPropType, 74 | cover: coverPropType, 75 | contrast: PropTypes.number, 76 | colors: colorsPropType, 77 | dither565: PropTypes.bool, 78 | flip: flipPropType, 79 | crop: cropPropType, 80 | fade: PropTypes.number, 81 | greyscale: PropTypes.bool, 82 | invert: PropTypes.bool, 83 | image: PropTypes.any.isRequired, 84 | normalize: PropTypes.bool, 85 | opacity: PropTypes.number, 86 | posterize: PropTypes.number, 87 | processedImage: PropTypes.func, 88 | opaque: PropTypes.bool, 89 | quality: PropTypes.number, 90 | rotate: rotatePropType, 91 | resize: resizePropType, 92 | sepia: PropTypes.bool, 93 | scale: PropTypes.number, 94 | scaleToFit: scaleToFitPropType, 95 | disableRerender: PropTypes.bool, 96 | customCdn: PropTypes.string, 97 | onProcessFinish: PropTypes.func, 98 | getImageRef: PropTypes.func 99 | }; 100 | 101 | export default MainPropTypes; 102 | -------------------------------------------------------------------------------- /src/worker.js: -------------------------------------------------------------------------------- 1 | import processImage from './utils/options'; 2 | const defaultCdn = 'https://unpkg.com/jimp@0.6.0/browser/lib/jimp.min.js'; 3 | 4 | export function process(data) { 5 | // how to ensure Jimp can work? 6 | return new Promise((resolve, reject) => { 7 | try { 8 | if (!Jimp) { 9 | } 10 | } catch (error) { 11 | const { customCdn } = data; 12 | const cdn = customCdn ? customCdn : defaultCdn; 13 | importScripts(cdn); 14 | } 15 | 16 | Jimp.read(data.image) 17 | .then(image => { 18 | processImage(image, data.props, Jimp).getBase64( 19 | Jimp.AUTO, 20 | (err, src) => { 21 | resolve({ src, err }); 22 | } 23 | ); 24 | }) 25 | .catch(err => { 26 | reject(err); 27 | }); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /webpack/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: path.resolve(__dirname, '../public/App.js'), 6 | mode: 'development', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname), 10 | publicPath: '/' 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.js$/, 16 | exclude: ['node_modules'], 17 | use: 'babel-loader' 18 | } 19 | ] 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 4 | 5 | const output = () => ({ 6 | filename: '[name].js', 7 | path: path.resolve(__dirname, '../build'), 8 | publicPath: '/', 9 | libraryTarget: 'umd', 10 | globalObject: 'this' 11 | }); 12 | 13 | const externals = () => ({ 14 | 'browser-image-size': 'browser-image-size', 15 | react: 'react', 16 | 'prop-types': 'prop-types', 17 | 'react-progressive-image': 'react-progressive-image', 18 | 'window-or-global': 'window-or-global' 19 | }); 20 | 21 | const jsLoader = () => ({ 22 | test: /\.js$/, 23 | include: path.resolve(__dirname, '../src'), 24 | exclude: ['node_modules', 'public'], 25 | use: 'babel-loader' 26 | }); 27 | 28 | const plugins = () => [ 29 | new webpack.LoaderOptionsPlugin({ 30 | minimize: true, 31 | debug: false 32 | }), 33 | new webpack.DefinePlugin({ 34 | 'process.env.NODE_ENV': 'production' 35 | }), 36 | new webpack.optimize.ModuleConcatenationPlugin(), 37 | new UglifyJSPlugin() 38 | ]; 39 | 40 | module.exports = { 41 | entry: path.resolve(__dirname, '../src/index.js'), 42 | mode: 'production', 43 | output: output(), 44 | target: 'web', 45 | externals: externals(), 46 | devtool: 'inline-source-map', 47 | module: { 48 | rules: [jsLoader()] 49 | }, 50 | plugins: plugins() 51 | }; 52 | --------------------------------------------------------------------------------