// Raster Mosaic — Adobe Illustrator compatible
// Turns a placed bitmap into vector art: the image is covered with a grid of cells and
// each cell is filled with the AVERAGE COLOUR of the pixels underneath it. This is the
// equivalent of the paper.js "Division Raster" example, and the demonstrating consumer of
// the pixel-colour API (RasterItem.size / getPixel / getAverageColor / pixelToDocument).
//
// USE: place a bitmap in the document, select it, run the script. Everything it draws goes
// into one group ("Raster Mosaic") and the whole run is a single undo step.
//
// TUNING: STEP is the cell size in document units — smaller = more detail and more shapes.
// SHAPE picks square or round cells; GAP leaves space between them (a halftone look).
#target illustrator

var STEP  = 24;       // cell size, in document units
var SHAPE = 'rect';   // 'rect' | 'circle'
var GAP   = 0;        // space left between cells, in document units

var doc = app.activeDocument;

// The image to work on: the selected one, or the only one in the document. Anything else is
// ambiguous, so say so instead of guessing.
function pickRaster() {
    var sel = doc.selection;
    for (var i = 0; sel && i < sel.length; i++)
        if (sel[i] && sel[i].typename === 'RasterItem') return sel[i];
    var all = doc.rasterItems;
    if (all.length === 1) return all[0];
    if (all.length > 1)
        throw new Error('Select the image you want to mosaic (' + all.length +
                        ' images in this document).');
    return null;
}

var raster = pickRaster();
if (!raster)
    throw new Error('Raster Mosaic needs a placed bitmap: add an image to the document, ' +
                    'select it, then run the script again.');

// The area to cover, in DOCUMENT coordinates. Taken from the image's own pixel corners
// through pixelToDocument, so it is correct even when the image (or a parent group) carries
// a transform — the placement, preserveAspectRatio and ancestor matrices are all folded in.
var size = raster.size;
var corners = [
    raster.pixelToDocument([0, 0]),
    raster.pixelToDocument([size.width, 0]),
    raster.pixelToDocument([size.width, size.height]),
    raster.pixelToDocument([0, size.height])
];
var left = corners[0][0], right = left, top = corners[0][1], bottom = top;
for (var i = 1; i < corners.length; i++) {
    left   = Math.min(left,   corners[i][0]);
    right  = Math.max(right,  corners[i][0]);
    top    = Math.min(top,    corners[i][1]);
    bottom = Math.max(bottom, corners[i][1]);
}

var cols = Math.max(1, Math.round((right - left) / STEP));
var rows = Math.max(1, Math.round((bottom - top) / STEP));

var group = doc.groupItems.add();
group.name = 'Raster Mosaic';

var made = 0, empty = 0;
for (var r = 0; r < rows; r++) {
    for (var c = 0; c < cols; c++) {
        var cellLeft = left + c * STEP;
        var cellTop  = top  + r * STEP;

        // The heart of the script: the average colour of the pixels under this cell.
        // Sampling is filtered by the cell's own area, so a cell only ever reads the
        // texels it actually covers. Cells outside the bitmap (or fully transparent
        // ones) come back null and are simply not drawn.
        var col = raster.getAverageColor([cellLeft, cellTop, STEP, STEP]);
        if (!col) { empty++; continue; }

        var side = STEP - GAP;
        if (side <= 0) side = STEP;
        var x = cellLeft + (STEP - side) / 2;
        var y = cellTop  + (STEP - side) / 2;

        var cell = (SHAPE === 'circle')
            ? group.pathItems.ellipse(y, x, side, side)
            : group.pathItems.rectangle(y, x, side, side);
        cell.fillColor = col;
        cell.stroked = false;
        made++;
    }
}

app.redraw();

// Report the size the USER can check: raster.width/height are the image AS PLACED in the
// document, the same numbers the Links palette shows for it. The bitmap's own pixel grid
// (raster.size) is a different quantity — worth printing, but only as a labelled extra,
// never as "the image is 256x188" when the object on the page is 96x70.
function round2(v) { return Math.round(v * 100) / 100; }
$.writeln('Raster Mosaic: ' + made + ' cell(s) in a ' + cols + 'x' + rows + ' grid (step ' +
          STEP + ') over a ' + round2(raster.width) + 'x' + round2(raster.height) +
          ' image (source ' + size.width + 'x' + size.height + ' px)' +
          (empty ? ', ' + empty + ' empty cell(s) skipped' : '') + '.');
