Copy the code of binksi_import_map.js in the console, then execute:
importMap(url);
Or to select a file on your computer:
importMap();
| function U32ColorToRGBA(n) { | |
| const u32a = new Uint32Array([n]); | |
| const u8a = new Uint8ClampedArray(u32a.buffer); | |
| return Array.from(u8a); | |
| } | |
| function U32ColorToHex(n) { | |
| const [r, g, b] = U32ColorToRGBA(n); | |
| const h = ("000000" + (r * 256 * 256 + g * 256 + b).toString(16)).slice(-6); | |
| return "#" + h; | |
| } | |
| class ImportedMap { | |
| tileIndex = 1; | |
| tiles = {}; | |
| rooms = []; | |
| mapColors = new Set(); | |
| constructor(img) { | |
| this.createTilesFromMapImage(img); | |
| } | |
| get transparentColor() { | |
| const [first] = this.mapColors; | |
| return first; | |
| } | |
| createTilesFromMapImage(img) { | |
| // Check the dimensions match a whole number of rooms. | |
| const roomSize = 16 * TILE_PX; | |
| if (img.height % roomSize !== 0 || img.width % roomSize !== 0) { | |
| throw new Error( | |
| `The dimensions of the map must be a multiple of ${roomSize}.` | |
| ); | |
| } | |
| // Create rooms and tiles. | |
| const roomCols = img.width / roomSize; | |
| const roomRows = img.height / roomSize; | |
| // Canvas so we can read pixels for color detection. | |
| const tileCtx = createRendering2D(TILE_PX, TILE_PX); | |
| for (let row = 0; row < roomRows; row++) { | |
| for (let col = 0; col < roomCols; col++) { | |
| const room = []; | |
| this.rooms.push(room); | |
| // Iterate over tiles in room | |
| for (let yRoom = 0; yRoom < 16; yRoom++) { | |
| const roomLine = []; | |
| room.push(roomLine); | |
| for (let xRoom = 0; xRoom < 16; xRoom++) { | |
| const yMap = row * 16 + yRoom; | |
| const xMap = col * 16 + xRoom; | |
| fillRendering2D(tileCtx); | |
| tileCtx.drawImage( | |
| img, | |
| xMap * TILE_PX, | |
| yMap * TILE_PX, | |
| TILE_PX, | |
| TILE_PX, | |
| 0, | |
| 0, | |
| TILE_PX, | |
| TILE_PX | |
| ); | |
| const tile = this.getOrCreateTile(tileCtx); | |
| roomLine.push(tile); | |
| } | |
| } | |
| } | |
| } | |
| if (this.mapColors.size > 7) { | |
| // FIXME: excluding transparency | |
| throw new Error("A map can have at most 7 colors."); | |
| } | |
| } | |
| getOrCreateTile(ctx) { | |
| const imageData = ctx.getImageData(0, 0, TILE_PX, TILE_PX); | |
| const uid = ctx.canvas.toDataURL("image/png"); | |
| if (!this.tiles[uid]) { | |
| this.tiles[uid] = this.createTile(uid, imageData); | |
| } | |
| return this.tiles[uid]; | |
| } | |
| createTile(uid, imageData) { | |
| const d = new Uint32Array(imageData.data.buffer); // endianness dependant! | |
| const colors = new Set(); | |
| // const counts = {}; | |
| for (let i = 0; i < d.length; i++) { | |
| const color = d[i]; | |
| colors.add(color); | |
| // counts[color] ??= 0; | |
| // counts[color]++; | |
| } | |
| if (colors.size > 2) { | |
| throw new Error("A tile can have at most 2 colors."); | |
| } | |
| for (const c of colors) { | |
| this.mapColors.add(c); | |
| } | |
| return { | |
| uid, | |
| index: this.tileIndex++, | |
| imageData, | |
| colors: [...colors], | |
| }; | |
| } | |
| async drawTileset(ctx) { | |
| ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); | |
| let x = 0, | |
| y = 0; | |
| for (const tile of Object.values(this.tiles)) { | |
| ctx.putImageData(tile.imageData, x, y); | |
| x += TILE_PX; | |
| if (x === 16 * TILE_PX) { | |
| x = 0; | |
| y += TILE_PX; | |
| } | |
| } | |
| // Ensure the tileset is white/transparent | |
| const transparentColor = this.transparentColor; | |
| withPixels(ctx, (pixels) => { | |
| for (let i = 0; i < pixels.length; i++) { | |
| if (pixels[i] === transparentColor) { | |
| pixels[i] = 0; | |
| } else { | |
| pixels[i] = 0xffffffff; | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| async function importMap(url) { | |
| if (typeof url !== "string") { | |
| const [file] = await maker.pickFiles("image/png"); | |
| url = URL.createObjectURL(file); | |
| } | |
| const map = new ImportedMap(await loadImage(url)); | |
| // Create bipsi rooms & tiles | |
| EDITOR.stateManager.makeChange(async (data) => { | |
| // New tileset | |
| const tileset = await EDITOR.forkTileset(); | |
| // Reset tiles & create new ones | |
| const tiles = Object.values(map.tiles); | |
| data.tiles = []; | |
| for (let i = 0; i < tiles.length; i++) { | |
| data.tiles.push({ id: tiles[i].index, frames: [i] }); | |
| } | |
| resizeTileset(tileset, data.tiles); | |
| EDITOR.tileBrowser.selectedTileIndex = 0; | |
| // Draw tiles on new tileset | |
| map.drawTileset(tileset); | |
| // Palette | |
| const colors = [...map.mapColors]; | |
| const palette = makeBlankPalette(0); | |
| data.palettes = [palette]; | |
| for (let i = 0; i < colors.length; i++) { | |
| palette.colors[i + 1] = U32ColorToHex(colors[i]); | |
| } | |
| // Rooms | |
| const transparentColor = map.transparentColor; | |
| map.rooms.forEach((mapRoom, i) => { | |
| const overwrittenRoom = data.rooms[i]; | |
| const bipsiRoom = makeBlankRoom(i + 1, data.palettes[0].id); | |
| if (overwrittenRoom) { | |
| bipsiRoom.events = overwrittenRoom.events; | |
| bipsiRoom.wallmap = overwrittenRoom.wallmap; | |
| } | |
| bipsiRoom.tilemap = mapRoom.map((line) => line.map((tile) => tile.index)); | |
| bipsiRoom.foremap = mapRoom.map((line) => | |
| line.map((tile) => { | |
| // find which one is not the transparent color | |
| let index; | |
| if (tile.colors[0] !== transparentColor) { | |
| index = colors.indexOf(tile.colors[0]); | |
| } else { | |
| index = colors.indexOf(tile.colors[1]); | |
| } | |
| return index + 1; | |
| }) | |
| ); | |
| data.rooms[i] = bipsiRoom; | |
| }); | |
| EDITOR.roomSelectWindow.select.selectedIndex = 0; | |
| EDITOR.requestRedraw(); | |
| }); | |
| } |