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.
npm i mosaic-image-stream
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}))
var Mosaic = require('mosaic-image-stream')
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
.
PRs accepted.
Small note: If editing the Readme, please conform to the standard-readme specification.
MIT © Gregor MacLennan / Digital Democracy