Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hvitis/11712d595b9b2c32f992026c7425cd72 to your computer and use it in GitHub Desktop.
Save hvitis/11712d595b9b2c32f992026c7425cd72 to your computer and use it in GitHub Desktop.
PaperJS with GatsbyJS to use Raster mosaic functionality.
import React from "react"
import { Form, Container, Row, Col, Badge } from "react-bootstrap"
import colors from "./utils/colors.json"
import BadgeColor from "./utils/BadgeColor.tsx"
import { IoIosCheckmarkCircle, IoIosCheckbox } from "react-icons/io"
import { formatAndDownloadBsxFile } from "./utils/formatBsxFile"
import { formatAndDownloadXmlFile } from "./utils/formatXmlFile"
import { formatAndDownloadLdrFile } from "./utils/formatLDrawFile"
class PaperCanvas extends React.Component {
constructor(props) {
super(props)
// if (typeof window !== `undefined`) {
// this.updateDimensions()
// }
this.fullColors = colors
this.NEAREST_COLOR_COMPARE = {}
colors.map(
color => (this.NEAREST_COLOR_COMPARE[color.bl_name.toString()] = color.hex_code)
)
this.coords = [
{
id: "16x16",
value: 16,
},
{
id: "32x32",
value: 32,
},
{
id: "46x46",
value: 46,
},
{
id: "48x48",
value: 48,
},
{
id: "64x64",
value: 64,
},
]
this.state = {
height: 900,
width: 900,
updated: false,
isCircle: true,
file: null,
newPhoto: false,
selectedBoardSize: 46,
colors: [],
hasFileNotUploadedError: false,
LDrawMatrix: [],
}
this.handleImageUpload = this.handleImageUpload.bind(this)
this.handleSelectDropdown = this.handleSelectDropdown.bind(this)
this.updateDimensions = this.updateDimensions.bind(this)
this.makeMosaic = this.makeMosaic.bind(this)
this.resizeMethod = this.resizeMethod.bind(this)
this.clearCanvas = this.clearCanvas.bind(this)
this.updateColors = this.updateColors.bind(this)
this.handleBsxSave = this.handleBsxSave.bind(this)
this.handleXmlSave = this.handleXmlSave.bind(this)
this.handleLdrSave = this.handleLdrSave.bind(this)
this.handleChangeShape = this.handleChangeShape.bind(this)
}
componentDidMount() {
if (typeof window !== `undefined`) {
let paper = require("paper")
paper.setup("paperCanvas")
var raster = new paper.Raster("mosaic")
raster.visible = false
raster.position = paper.view.center
}
window.addEventListener("resize", this.resizeMethod)
}
makeMosaic(callback) {
if (!this.state.file) {
this.setState({ hasFileNotUploadedError: true })
return
}
this.setState({ hasFileNotUploadedError: false })
this.clearCanvas()
let paper = require("paper")
var nearestColor = require("nearest-color").from(this.NEAREST_COLOR_COMPARE)
// Create a raster item using the image tag with id='mona'
var raster = new paper.Raster("mosaic")
// Hide the Raster:
raster.position = paper.view.center
raster.visible = false
// The size of our grid cells:
var gridSize = this.state.selectedBoardSize
// Space the cells by 120%:
var spacing = 19
// As the web is asynchronous, we need to wait for the raster to load
// before we can perform any operation on its pixels.
// passing type of points to raster
raster.isCircle = this.state.isCircle
raster.fullColors = this.fullColors
raster.on("load", function() {
let colorCodes = []
// This verification array is an additional copy of colorCodes array
// in order to push just hex codes strings and not Objects for later filtering
// this should be refactored
let colorCodesVerify = []
// LDraw matrix
let LDrawMatrix = []
// Since the example image we're using is much too large,
// and therefore has way too many pixels, lets downsize it to
// 40 pixels wide and 30 pixels high:
raster.size = new paper.Size(gridSize, gridSize)
// LDraw board is x, y, z coordinates. Here we start by placing first x coordinate
// that we will be increasing during iteration.
let LDrawXCoord = 10
let LDrawYCoord = -24
for (var x = 0; x < raster.width; x++) {
let verticalRowBotToTop = []
let LDrawZCoord = -10
for (var y = raster.height - 1; y >= 0; y--) {
// Get the color of the pixel:
var color = raster.getPixel(x, y)
if (raster.isCircle) {
// Create a circle ART MOSAIC shaped path:
var path = new paper.Path.Circle({
center: new paper.Point(x * spacing, y * spacing),
// center: paper.view.center,
// radius: gridSize / 2 / spacing,
radius: 9,
})
} else {
// Create a square PORTRAIT shaped path:
var path = new paper.Path.Rectangle({
point: new paper.Point(x * spacing, y * spacing),
// center: paper.view.center,
// radius: gridSize / 2 / spacing,
size: 18,
})
}
// Set the fill color of the path to the color
// of the pixel:
let singleColor = {}
let hexColor = color.toCSS(true)
let pickedColor = nearestColor(hexColor)
let filteredColour = raster.fullColors.filter(
color => color.hex_code === pickedColor.value
)
if (!colorCodesVerify.includes(pickedColor.value)) {
/* colors contains already the color we're iterating */
colorCodesVerify.push(pickedColor.value)
singleColor["hex_code"] = pickedColor.value
singleColor["name"] = pickedColor.name
singleColor["bl_id"] = filteredColour[0].bl_id
singleColor["amount"] = 1
colorCodes.push(singleColor)
} else {
// Getting the color code from the array of colors that will be used for BUTTONS
let newFilteredColour = colorCodes.filter(color => color.hex_code === pickedColor.value)
console.log(newFilteredColour)
newFilteredColour[0]["amount"] += 1
}
// Appending a LDraw color value to vertical row
verticalRowBotToTop.push({
x: LDrawXCoord,
y: LDrawYCoord,
z: LDrawZCoord,
color: filteredColour[0].ldraw_id,
})
// Increasing vertical location of a next piece
LDrawZCoord += 20
path.fillColor = pickedColor.value
}
LDrawMatrix.push(verticalRowBotToTop)
// Increasing horizontal location of next piece
LDrawXCoord += 20
}
paper.project.activeLayer.position = paper.view.center
// Returning colors array to create buttons with infromation and
// Returning LDraw matrix of color IDs for LDraw to draw board
callback(colorCodes, LDrawMatrix)
})
}
clearCanvas() {
if (typeof window !== `undefined`) {
let paper = require("paper")
paper.project.activeLayer.removeChildren()
paper.project.clear()
}
}
updateDimensions() {
this.setState({
height: window.innerWidth,
width: window.innerWidth,
})
}
resizeMethod() {
// this.updateDimensions()
this.makeMosaic(this.updateColors)
}
updateColors(colors, LDrawMatrix) {
// Order here colours by amount of them in the picture
colors.sort((a, b) =>
a.amount > b.amount ? 1 : b.amount > a.amount ? -1 : 0
)
console.log(LDrawMatrix)
this.setState({ colors: colors, LDrawMatrix: LDrawMatrix })
}
handleImageUpload(event) {
this.setState({
file: URL.createObjectURL(event.target.files[0]),
})
}
handleSelectDropdown(event) {
this.setState({
selectedBoardSize: event.target.value,
})
}
handleBsxSave() {
formatAndDownloadBsxFile(this.state.colors)
}
handleLdrSave() {
formatAndDownloadLdrFile(this.state.LDrawMatrix)
}
handleXmlSave() {
formatAndDownloadXmlFile(this.state.colors)
}
handleCanvasSave() {
var FileSaver = require("file-saver")
var canvas = document.getElementById("paperCanvas")
canvas.toBlob(function(blob) {
FileSaver.saveAs(blob, "LEGO-Art.png")
})
}
handleChangeShape() {
this.setState({ isCircle: !this.state.isCircle })
}
render() {
return (
<div>
<Container>
<LEGORow>
<StyledCol>
<Form.Label>Select image:</Form.Label>
<Form.Group>
<Form.Group id="center">
<StyledInput
id="file"
type="file"
onChange={this.handleImageUpload}
/>
</Form.Group>
</Form.Group>
</StyledCol>
<StyledCol>
<Form.Label>Board size:</Form.Label>
<Form.Group controlId="SelectToBucket">
<StyledSelect
required
type="text"
as="select"
size="lg"
onChange={this.handleSelectDropdown}
name="selectedToBucket"
value={this.state.selectedBoardSize}
>
{this.coords.map(sizeOfBoard => (
<StyledOption
key={sizeOfBoard.id}
value={sizeOfBoard.value}
>
{sizeOfBoard.id}
</StyledOption>
))}
</StyledSelect>
</Form.Group>
</StyledCol>
<StyledCol>
<StyledDiv>
<Form.Label>Tile shape:</Form.Label>
</StyledDiv>
<StyledIcon
onClick={() => {
this.handleChangeShape()
}}
>
{this.state.isCircle ? (
<IoIosCheckmarkCircle />
) : (
<IoIosCheckbox />
)}
</StyledIcon>
</StyledCol>
</LEGORow>
</Container>
<StyledContainer>
<GenerateButton onClick={() => this.makeMosaic(this.updateColors)}>
Generate
</GenerateButton>
</StyledContainer>
{this.state.hasFileNotUploadedError ? (
<StyledContainer>
<DangerMark>Please select an image first!</DangerMark>
</StyledContainer>
) : (
<></>
)}
{this.state.colors.length !== 0 ? (
<>
<StyledContainer>
<StyledParagraph>
<Tooltip>
<InfoMark>Scroll down</InfoMark> to learn how to
<InfoMark>use</InfoMark> .bsx and .xml
<InfoMark>files</InfoMark>
</Tooltip>
Download
</StyledParagraph>
</StyledContainer>
<StyledContainer>
<DownloadButton onClick={() => this.handleBsxSave()}>
.bsx file
</DownloadButton>
<DownloadButton onClick={() => this.handleXmlSave()}>
.xml file
</DownloadButton>
<DownloadButton onClick={() => this.handleLdrSave()}>
.ldr file
</DownloadButton>
<DownloadButton onClick={() => this.handleCanvasSave()}>
.png image
</DownloadButton>
</StyledContainer>
</>
) : (
<StyledContainer>
Generate art to get
<StyledParagraph>
<Tooltip>
<InfoMark>.png</InfoMark> file is your
<InfoMark>mosaic</InfoMark> image to{" "}
<InfoMark>Download</InfoMark>
</Tooltip>
.png
</StyledParagraph>{" "}
or
<StyledParagraph>
<Tooltip>
<InfoMark>.xml</InfoMark> file you can use in various places to
<InfoMark>download</InfoMark> list of needed
<InfoMark>pieces</InfoMark>
</Tooltip>
.xml
</StyledParagraph>
or
<StyledParagraph>
<Tooltip>
<InfoMark>.bsx</InfoMark> file is used to make
<InfoMark>orders</InfoMark> on
<InfoMark>BrickLink.com</InfoMark>
</Tooltip>
.bsx
</StyledParagraph>
</StyledContainer>
)}
<Container>
{this.state.colors.length !== 0 ? (
<StyledParagraph>
<Tooltip>
<InfoMark>Hover</InfoMark> on each item to see
<InfoMark>amount</InfoMark> of pieces used in the
<InfoMark>mosaic</InfoMark>
</Tooltip>
List of colors :
</StyledParagraph>
) : (
<></>
)}
{this.state.colors.map(color => (
<BadgeColor color={color} />
))}
{this.state.colors.length !== 0 ? (
<StyledParagraph>
<Tooltip>
<InfoMark>Average</InfoMark> price of
<InfoMark>all</InfoMark> pieces.
</Tooltip>
Estimated price :{" "}
{`${this.state.selectedBoardSize *
this.state.selectedBoardSize *
0.04} $`}
</StyledParagraph>
) : (
<></>
)}
</Container>
<LEGORow>
<img
src={this.state.file}
alt="WOMAN"
crossOrigin="*"
ref="mosaic"
id="mosaic"
hidden
/>
<canvas
id="paperCanvas"
height={this.state.height}
width={this.state.width}
></canvas>
</LEGORow>
</div>
)
}
}
export default PaperCanvas
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment