Skip to content

Streaming mosaic of multiple images into a single image

Notifications You must be signed in to change notification settings

digidem/mosaic-image-stream

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mosaic-image-stream

npm js-standard-style

Streaming mosaic of multiple images into a single image

Take a 2-D array of input pixel-streams and mosaic them together into a single image. Everything is streams, so you can theoretically mosaic thousands of images into a megapixel image without much memory usage.

Table of Contents

Install

npm i mosaic-image-stream

Usage

See examples.

Mosaic local file streams:

var fs = require('fs')
var path = require('path')
var PNGDecoder = require('png-stream/decoder')
var PNGEncoder = require('png-stream/encoder')

var Mosaic = require('../')

var baseName = path.join(__dirname, 'images/image')

var streams = [
  [
    fs.createReadStream(baseName + '1.png').pipe(new PNGDecoder()),
    fs.createReadStream(baseName + '2.png').pipe(new PNGDecoder()),
    fs.createReadStream(baseName + '3.png').pipe(new PNGDecoder())
  ],
  [
    fs.createReadStream(baseName + '4.png').pipe(new PNGDecoder()),
    fs.createReadStream(baseName + '5.png').pipe(new PNGDecoder()),
    fs.createReadStream(baseName + '6.png').pipe(new PNGDecoder())
  ],
  [
    fs.createReadStream(baseName + '7.png').pipe(new PNGDecoder()),
    fs.createReadStream(baseName + '8.png').pipe(new PNGDecoder()),
    fs.createReadStream(baseName + '9.png').pipe(new PNGDecoder())
  ]
]

Mosaic(streams, {height: 300})
  .pipe(new PNGEncoder())
  .pipe(fs.createWriteStream(path.join(__dirname, 'file_mosaic.png')))

The giant wallmap of the Middle East you've always wanted (17,000px x 17,000px):

var fs = require('fs')
var path = require('path')
var request = require('request')
var JPGDecoder = require('jpg-stream/decoder')
var JPGEncoder = require('jpg-stream/encoder')
var tilebelt = require('tilebelt')

var MAPBOX_TOKEN = 'pk.eyJ1IjoiZ21hY2xlbm5hbiIsImEiOiJSaWVtd2lRIn0.ASYMZE2HhwkAw4Vt7SavEg'
var urlBase = 'https://api.mapbox.com/v4/mapbox.streets/'

var Mosaic = require('../')

var zoom = 8
var tl = tilebelt.pointToTile(24, 48, zoom)
var br = tilebelt.pointToTile(72, 9, zoom)

var size = [br[0] - tl[0] + 1, br[1] - tl[1] + 1]
var factories = Array(size[0]).fill().map((v, i) => {
  var count = 0
  return function (cb) {
    var x = tl[0] + i
    var y = tl[1] + count
    if (++count > size[1]) return cb(null, null)
    var url = urlBase + zoom + '/' + x + '/' + y + '@2x.jpg?access_token=' + MAPBOX_TOKEN
    cb(null, request(url).pipe(new JPGDecoder()).on('error', err => {
      console.error(url)
      console.error(err)
    }))
  }
})

Mosaic(factories, size[1] * 512)
  .on('error', console.error)
  .pipe(new JPGEncoder())
  .pipe(fs.createWriteStream(path.join(__dirname, 'map_mosaic.jpg')))

Mosaic a whole bunch of images from Flickr:

var fs = require('fs')
var path = require('path')
var request = require('request')
var JPEGDecoder = require('jpg-stream/decoder')
var JPEGEncoder = require('jpg-stream/encoder')

var Mosaic = require('../')

var reqUrl = 'https://api.flickr.com/services/rest/?' +
  'method=flickr.photos.search&' +
  'api_key=ea621d507593aa247dcaa792268b93d7&' +
  'tags=portrait&' +
  'sort=interestingness-desc&' +
  'media=photos&' +
  'extras=url_q&' +
  'format=json&' +
  'nojsoncallback=1&' +
  'per_page=500'

// One of the images Flickr returns does not have a height of 150px, even though the Flickr API thinks it does
var badUrl = 'https://farm2.staticflickr.com/1554/24516806801_084046c4dc_q.jpg'

var size = [15, 15]

request(reqUrl, function (err, resp, body) {
  if (err) return console.error(err)
  var urls = JSON.parse(body).photos.photo
    .map(d => d.url_q)
    .filter(d => d !== badUrl)
  var factories = Array(size[0]).fill().map((v, i) => {
    var count = 0
    return function (cb) {
      var url = urls[count + i * size[1]]
      if (++count > size[1]) return cb(null, null)
      cb(null, request(url).pipe(new JPEGDecoder()))
    }
  })

  Mosaic(factories, {height: size[1] * 150})
    .on('error', console.error)
    .pipe(new JPEGEncoder())
    .pipe(fs.createWriteStream(path.join(__dirname, 'flickr_mosaic.jpg')))
})

Input streams must be pixel-streams. If you want to stream raw image data, stream it through a pixel-stream constructed with your image width, height and color space:

myRawImageStream.pipe(new PixelStream(myImageWidth, myImageHeight, {colorSpace: myImageColorSpace}))

API

var Mosaic = require('mosaic-image-stream')

Mosaic(streams, height)

Where:

  • streams - a 2-d array of input stream, columns, then rows: e.g. streams = [[row1col1, row2col2], [row1col2, row2col2]]
  • height - the total height of the input streams (width can be calculated on the fly from input images)

All input streams must have the same dimensions and color space - the output stream will throw an error if they differ, or if the total height of the input streams does not match the height specified.

Returns a pixel-stream.

Contribute

PRs accepted.

Small note: If editing the Readme, please conform to the standard-readme specification.

License

MIT © Gregor MacLennan / Digital Democracy

About

Streaming mosaic of multiple images into a single image

Resources

Stars

Watchers

Forks

Packages

No packages published