Nodejs抽出画像のテーマ色(一)


需要
  • 正常にアクセスできる画像リンク
  • は、ピクチャデータ値のグレー系rgbの値(すなわちr=g=bの値)
  • を排除する.
  • .残りのrgb値を集計して、色ランキング
  • を導出した.
  • ここでは主に参考になります.https://github.com/lokesh/color-thief オープンソース
  • 下編はNodejsに会ってピクチャーのテーマ色を抽出します(2)
  • 使い方
    ColorThief.getColor('https://lokeshdhakar.com/projects/color-thief/images/image-1.jpg')
      .then((color) => {
         
        console.log('color', color); //      (      )
      })
      .catch((err) => {
         
        console.log(err);
      });
    
    ColorThief.getPalette('https://lokeshdhakar.com/projects/color-thief/images/image-1.jpg', 5)
      .then((palette) => {
         
        console.log('palette', palette); //        (     5  )
      })
      .catch((err) => {
         
        console.log(err);
      });
    
    どのように画像データ値のグレー系rgbの値を削除しますか?
    ここは画像の色の値を統計するところでブロック処理をします.
    //        color-thief     color-thief-node.js      
    
    const quantize = require('quantize');
    const getPixels = require('./node-pixels');
    
    function createPixelArray(imgData, pixelCount, quality) {
         
        const pixels = imgData;
        const pixelArray = [];
    
        for (let i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) {
         
            offset = i * 4;
            r = pixels[offset + 0];
            g = pixels[offset + 1];
            b = pixels[offset + 2];
            a = pixels[offset + 3];
    
            if (typeof a === 'undefined' || a >= 125) {
         
                //       
                if (r !== g || r !== b) {
         
                    pixelArray.push([r, g, b]);
                }
            }
        }
        return pixelArray;
    }
    
    function validateOptions(options) {
         
        let {
          colorCount, quality } = options;
    
        if (typeof colorCount === 'undefined' || !Number.isInteger(colorCount)) {
         
            colorCount = 10;
        } else if (colorCount === 1 ) {
         
            throw new Error('colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()');
        } else {
         
            colorCount = Math.max(colorCount, 2);
            colorCount = Math.min(colorCount, 20);
        }
    
        if (typeof quality === 'undefined' || !Number.isInteger(quality) || quality < 1) {
         
            quality = 10;
        }
    
        return {
         
            colorCount,
            quality
        }
    }
    
    function loadImg(img) {
         
        return new Promise((resolve, reject) => {
         
            getPixels(img, function(err, data) {
         
                if(err) {
         
                    reject(err)
                } else {
         
                    resolve(data);
                }
            })
        });
    }
    
    function getColor(img, quality) {
         
        return new Promise((resolve, reject) => {
         
            getPalette(img, 5, quality)
                .then(palette => {
         
                    resolve(palette[0]);
                })
                .catch(err => {
         
                    reject(err);
                })
        });
    
    }
    
    function getPalette(img, colorCount = 10, quality = 10) {
         
        const options = validateOptions({
         
            colorCount,
            quality
        });
    
        return new Promise((resolve, reject) => {
         
            loadImg(img)
                .then(imgData => {
         
                    const pixelCount = imgData.shape[0] * imgData.shape[1];
                    const pixelArray = createPixelArray(imgData.data, pixelCount, options.quality);
    
                    const cmap = quantize(pixelArray, options.colorCount);
                    const palette = cmap? cmap.palette() : null;
    
                    resolve(palette);
                })
                .catch(err => {
         
                    reject(err);
                })
        });
    }
    
    module.exports = {
         
        getColor,
        getPalette
    };
    
    上のファイルでは、グレー系の値を排除するロジックを修正した以外に、もう一つの違いがあります.
    const getPixels = require('./node-pixels');
    
    テキストの使用
    const getPixels = require('get-pixels');
    
    私たちはget-pixelsのこのカバンにいくつかの追加的な修正が必要です.
    修正の原因は
  • ピクチャのmimeTypeは、image/jpeg;charset=UTF-8などの文字コードを有することがあり得るが、期待されるのはimage/jpeg
  • である.
  • は、jpg、jpegのピクチャ処理について、しばしばエラーを報告しています.特に、第三者によって処理されたピクチャ、例えば(裁断、つなぎ合わせをした後)、エラーを報告するのはthrow new Error("SOI not found");で、jpgピクチャ規格に合わないという意味です.
    // node-pixels.js
    'use strict';
    
    var ndarray = require('ndarray');
    var path = require('path');
    var PNG = require('pngjs').PNG;
    // var jpeg = require('jpeg-js'); //    jpg、jpeg      ,    jpeg-js      ,     images    
    var pack = require('ndarray-pack');
    var GifReader = require('omggif').GifReader;
    var Bitmap = require('node-bitmap');
    var fs = require('fs');
    var request = require('request');
    var mime = require('mime-types');
    var parseDataURI = require('parse-data-uri');
    var images = require('images'); //    images   jpg、jpeg
    
    function handlePNG(data, cb) {
         
      var png = new PNG();
      png.parse(data, function (err, img_data) {
         
        if (err) {
         
          cb(err);
          return;
        }
        cb(
          null,
          ndarray(
            new Uint8Array(img_data.data),
            [img_data.width | 0, img_data.height | 0, 4],
            [4, (4 * img_data.width) | 0, 1],
            0
          )
        );
      });
    }
    
    function handleJPEG(data, cb) {
         
      var jpegData;
      var imageData;
      try {
         
        // jpegData = jpeg.decode(data);     jpeg-js      ,   images     
        imageData = images(data);
        jpegData = imageData.encode('jpg'); //    jpg      ,       2
      } catch (e) {
         
        cb(e);
        return;
      }
      if (!jpegData) {
         
        cb(new Error('Error decoding jpeg'));
        return;
      }
      var nshape = [imageData.height(), imageData.width(), 4];
      var result = ndarray(jpegData, nshape);
      cb(null, result.transpose(1, 0));
    }
    
    function handleGIF(data, cb) {
         
      var reader;
      try {
         
        reader = new GifReader(data);
      } catch (err) {
         
        cb(err);
        return;
      }
      if (reader.numFrames() > 0) {
         
        var nshape = [reader.numFrames(), reader.height, reader.width, 4];
        try {
         
          var ndata = new Uint8Array(nshape[0] * nshape[1] * nshape[2] * nshape[3]);
        } catch (err) {
         
          cb(err);
          return;
        }
        var result = ndarray(ndata, nshape);
        try {
         
          for (var i = 0; i < reader.numFrames(); ++i) {
         
            reader.decodeAndBlitFrameRGBA(
              i,
              ndata.subarray(result.index(i, 0, 0, 0), result.index(i + 1, 0, 0, 0))
            );
          }
        } catch (err) {
         
          cb(err);
          return;
        }
        cb(null, result.transpose(0, 2, 1));
      } else {
         
        var nshape = [reader.height, reader.width, 4];
        var ndata = new Uint8Array(nshape[0] * nshape[1] * nshape[2]);
        var result = ndarray(ndata, nshape);
        try {
         
          reader.decodeAndBlitFrameRGBA(0, ndata);
        } catch (err) {
         
          cb(err);
          return;
        }
        cb(null, result.transpose(1, 0));
      }
    }
    
    function handleBMP(data, cb) {
         
      var bmp = new Bitmap(data);
      try {
         
        bmp.init();
      } catch (e) {
         
        cb(e);
        return;
      }
      var bmpData = bmp.getData();
      var nshape = [bmpData.getHeight(), bmpData.getWidth(), 4];
      var ndata = new Uint8Array(nshape[0] * nshape[1] * nshape[2]);
      var result = ndarray(ndata, nshape);
      pack(bmpData, result);
      cb(null, result.transpose(1, 0));
    }
    
    function doParse(mimeType, data, cb) {
         
      //   mime      ,       1
      if (mimeType.includes(';')) {
         
        mimeType = mimeType.split(';')[0];
      }
      switch (mimeType) {
         
        case 'image/png':
          handlePNG(data, cb);
          break;
    
        case 'image/jpg':
        case 'image/jpeg':
          handleJPEG(data, cb);
          break;
    
        case 'image/gif':
          handleGIF(data, cb);
          break;
    
        case 'image/bmp':
          handleBMP(data, cb);
          break;
    
        default:
          cb(new Error('Unsupported file type: ' + mimeType));
      }
    }
    
    module.exports = function getPixels(url, type, cb) {
         
      if (!cb) {
         
        cb = type;
        type = '';
      }
      if (Buffer.isBuffer(url)) {
         
        if (!type) {
         
          cb(new Error('Invalid file type'));
          return;
        }
        doParse(type, url, cb);
      } else if (url.indexOf('data:') === 0) {
         
        try {
         
          var buffer = parseDataURI(url);
          if (buffer) {
         
            process.nextTick(function () {
         
              doParse(type || buffer.mimeType, buffer.data, cb);
            });
          } else {
         
            process.nextTick(function () {
         
              cb(new Error('Error parsing data URI'));
            });
          }
        } catch (err) {
         
          process.nextTick(function () {
         
            cb(err);
          });
        }
      } else if (url.indexOf('http://') === 0 || url.indexOf('https://') === 0) {
         
        request({
          url: url, encoding: null }, function (err, response, body) {
         
          if (err) {
         
            cb(err);
            return;
          }
    
          type = type;
          if (!type) {
         
            if (response.getHeader !== undefined) {
         
              type = response.getHeader('content-type');
            } else if (response.headers !== undefined) {
         
              type = response.headers['content-type'];
            }
          }
          if (!type) {
         
            cb(new Error('Invalid content-type'));
            return;
          }
          doParse(type, body, cb);
        });
      } else {
         
        fs.readFile(url, function (err, data) {
         
          if (err) {
         
            cb(err);
            return;
          }
          type = type || mime.lookup(url);
          if (!type) {
         
            cb(new Error('Invalid file type'));
            return;
          }
          doParse(type, data, cb);
        });
      }
    };
    
    
    2を修復する時にimagesというカバンを使いました.そうすると、次の色ディスクの値が得られます.
    [ [ 195, 170, 172 ],
      [ 36, 154, 139 ],
      [ 184, 35, 149 ],
      [ 174, 186, 34 ],
      [ 95, 171, 171 ] ]
    
    実際の写真と比較したところ、この色盤の値は多く一致していないことが分かりました.しかも、差がはっきりしています.
    この中の具体的な問題はimagesにあり、コードの最適化を経て、画像と一致する値が生成された.
    [ [ 125, 190, 192 ],
      [ 213, 191, 131 ],
      [ 53, 37, 27 ],
      [ 129, 123, 58 ],
      [ 42, 125, 148 ] ]
    
    コード最適化はNodejsを見て写真の主題色を抽出する(二)