From cefaf71c2565be0e8debee712d6fc7d860de34d1 Mon Sep 17 00:00:00 2001 From: Supreme2580 Date: Thu, 21 Nov 2024 13:55:52 +0100 Subject: [PATCH 1/8] increased max image sizes to 256 --- backend/routes/templates.go | 4 ++-- frontend/src/canvas/NFTSelector.js | 16 ++++++++-------- frontend/src/tabs/factions/FactionItem.js | 4 ++-- frontend/src/tabs/templates/Templates.js | 4 ++-- onchain/src/art_peace.cairo | 6 +++--- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/backend/routes/templates.go b/backend/routes/templates.go index a07aa3c3..365c96a6 100644 --- a/backend/routes/templates.go +++ b/backend/routes/templates.go @@ -298,7 +298,7 @@ func addTemplateImg(w http.ResponseWriter, r *http.Request) { } bounds := img.Bounds() width, height := bounds.Max.X-bounds.Min.X, bounds.Max.Y-bounds.Min.Y - if width < 5 || width > 64 || height < 5 || height > 64 { + if width < 5 || width > 256 || height < 5 || height > 256 { routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid image dimensions") return } @@ -372,7 +372,7 @@ func addTemplateData(w http.ResponseWriter, r *http.Request) { return } - if width < 5 || width > 64 || height < 5 || height > 64 { + if width < 5 || width > 256 || height < 5 || height > 256 { routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid image dimensions") return } diff --git a/frontend/src/canvas/NFTSelector.js b/frontend/src/canvas/NFTSelector.js index 646b3cba..e3974cbd 100644 --- a/frontend/src/canvas/NFTSelector.js +++ b/frontend/src/canvas/NFTSelector.js @@ -76,14 +76,14 @@ const NFTSelector = (props) => { let width = endX - startX; let height = endY - startY; // Max NFT sizes - if (width > 64) { - width = 64; + if (width > 256) { + width = 256; if (x < initX) { startX = endX - width; } } - if (height > 64) { - height = 64; + if (height > 256) { + height = 256; if (y < initY) { startY = endY - height; } @@ -190,14 +190,14 @@ const NFTSelector = (props) => { let width = endX - startX; let height = endY - startY; // Max NFT sizes - if (width > 64) { - width = 64; + if (width > 256) { + width = 256; if (x < initX) { startX = endX - width; } } - if (height > 64) { - height = 64; + if (height > 256) { + height = 256; if (y < initY) { startY = endY - height; } diff --git a/frontend/src/tabs/factions/FactionItem.js b/frontend/src/tabs/factions/FactionItem.js index 30a1c79a..d105ec05 100644 --- a/frontend/src/tabs/factions/FactionItem.js +++ b/frontend/src/tabs/factions/FactionItem.js @@ -238,9 +238,9 @@ const FactionItem = (props) => { ); return; } - if (height > 64 || width > 64) { + if (height > 256 || width > 256) { alert( - 'Image is too large, maximum size is 64x64. Given size is ' + + 'Image is too large, maximum size is 256x256. Given size is ' + width + 'x' + height diff --git a/frontend/src/tabs/templates/Templates.js b/frontend/src/tabs/templates/Templates.js index 6708784b..4da9b1f8 100644 --- a/frontend/src/tabs/templates/Templates.js +++ b/frontend/src/tabs/templates/Templates.js @@ -91,9 +91,9 @@ const TemplatesMainSection = (props) => { ); return; } - if (height > 64 || width > 64) { + if (height > 256 || width > 256) { alert( - 'Image is too large, maximum size is 64x64. Given size is ' + + 'Image is too large, maximum size is 256x256. Given size is ' + width + 'x' + height diff --git a/onchain/src/art_peace.cairo b/onchain/src/art_peace.cairo index b179d06a..7bae6e4b 100644 --- a/onchain/src/art_peace.cairo +++ b/onchain/src/art_peace.cairo @@ -926,7 +926,7 @@ pub mod ArtPeace { template_metadata.position < self.canvas_width.read() * self.canvas_height.read(), 'Template position out of bounds' ); - let MAX_TEMPLATE_SIZE: u128 = 64; + let MAX_TEMPLATE_SIZE: u128 = 256; let MIN_TEMPLATE_SIZE: u128 = 5; assert( template_metadata.width >= MIN_TEMPLATE_SIZE @@ -971,7 +971,7 @@ pub mod ArtPeace { template_metadata.position < self.canvas_width.read() * self.canvas_height.read(), 'Template position out of bounds' ); - let MAX_TEMPLATE_SIZE: u128 = 64; + let MAX_TEMPLATE_SIZE: u128 = 256; let MIN_TEMPLATE_SIZE: u128 = 5; assert( template_metadata.width >= MIN_TEMPLATE_SIZE @@ -1091,7 +1091,7 @@ pub mod ArtPeace { self.check_game_running(); // TODO: To config? let MIN_NFT_SIZE: u128 = 1; - let MAX_NFT_SIZE: u128 = 64; + let MAX_NFT_SIZE: u128 = 256; assert( mint_params.width >= MIN_NFT_SIZE && mint_params.width <= MAX_NFT_SIZE, 'NFT width out of bounds' From bbcd3d150a20541b66321819bbf04941b1244da4 Mon Sep 17 00:00:00 2001 From: Supreme2580 Date: Thu, 21 Nov 2024 23:27:11 +0100 Subject: [PATCH 2/8] moving down data --- frontend/src/tabs/TabPanel.js | 4 ++++ frontend/src/tabs/nfts/NFTItem.js | 22 ++++++++++++++++++++++ frontend/src/tabs/nfts/NFTs.js | 8 ++++++++ frontend/src/tabs/templates/Templates.js | 2 ++ 4 files changed, 36 insertions(+) diff --git a/frontend/src/tabs/TabPanel.js b/frontend/src/tabs/TabPanel.js index e602218d..9f084dd2 100644 --- a/frontend/src/tabs/TabPanel.js +++ b/frontend/src/tabs/TabPanel.js @@ -246,6 +246,8 @@ const TabPanel = (props) => { setTemplateImage={props.setTemplateImage} setTemplateColorIds={props.setTemplateColorIds} setActiveTab={props.setActiveTab} + setTemplateOverlayMode={props.setTemplateOverlayMode} + setOverlayTemplate={props.setOverlayTemplate} /> )} @@ -265,6 +267,8 @@ const TabPanel = (props) => { queryAddress={props.queryAddress} isMobile={props.isMobile} gameEnded={props.gameEnded} + setTemplateOverlayMode={props.setTemplateOverlayMode} + setOverlayTemplate={props.setOverlayTemplate} /> )} diff --git a/frontend/src/tabs/nfts/NFTItem.js b/frontend/src/tabs/nfts/NFTItem.js index 712a01e0..f956f215 100644 --- a/frontend/src/tabs/nfts/NFTItem.js +++ b/frontend/src/tabs/nfts/NFTItem.js @@ -145,6 +145,27 @@ const NFTItem = (props) => { }, [props.minter]); const [showInfo, setShowInfo] = React.useState(false); + const handleNftClick = (e) => { + if ( + e.target.classList.contains('NFTItem__button') || + e.target.classList.contains('Like__icon') || + e.target.classList.contains('Share__icon') + ) { + return; + } + // Format NFT data to match template structure + const nftTemplate = { + position: props.position, + width: props.width, + height: props.height, + image: props.image, + isNft: true, // Add flag to identify as NFT + tokenId: props.tokenId + }; + props.setTemplateOverlayMode(true); + props.setOverlayTemplate(nftTemplate); + props.setActiveTab('Canvas'); + }; return (
@@ -153,6 +174,7 @@ const NFTItem = (props) => { src={props.image} alt={`nft-image-${props.tokenId}`} className='NFTItem__image' + onClick={handleNftClick} />

{props.name}

diff --git a/frontend/src/tabs/nfts/NFTs.js b/frontend/src/tabs/nfts/NFTs.js index 40c617f1..aebcc33a 100644 --- a/frontend/src/tabs/nfts/NFTs.js +++ b/frontend/src/tabs/nfts/NFTs.js @@ -77,6 +77,9 @@ const NFTsMainSection = (props) => { minter={nft.minter} queryAddress={props.queryAddress} updateLikes={props.updateLikes} + setTemplateOverlayMode={props.setTemplateOverlayMode} + setOverlayTemplate={props.setOverlayTemplate} + setActiveTab={props.setActiveTab} /> ); })} @@ -140,6 +143,9 @@ const NFTsExpandedSection = (props) => { minter={nft.minter} queryAddress={props.queryAddress} updateLikes={props.updateLikes} + setTemplateOverlayMode={props.setTemplateOverlayMode} + setOverlayTemplate={props.setOverlayTemplate} + setActiveTab={props.setActiveTab} /> ); })} @@ -334,6 +340,8 @@ const NFTs = (props) => { filters={filters} isMobile={props.isMobile} gameEnded={props.gameEnded} + setTemplateOverlayMode={props.setTemplateOverlayMode} + setOverlayTemplate={props.setOverlayTemplate} /> ); }; diff --git a/frontend/src/tabs/templates/Templates.js b/frontend/src/tabs/templates/Templates.js index 4da9b1f8..e00bdf5d 100644 --- a/frontend/src/tabs/templates/Templates.js +++ b/frontend/src/tabs/templates/Templates.js @@ -460,6 +460,8 @@ const Templates = (props) => { setTemplateColorIds={props.setTemplateColorIds} availableTemplates={availableTemplates} setActiveTab={props.setActiveTab} + setTemplateOverlayMode={props.setTemplateOverlayMode} + setOverlayTemplate={props.setOverlayTemplate} /> ); }; From 75c145afa3479be4a8500e8e850c0139a704dc5f Mon Sep 17 00:00:00 2001 From: Supreme2580 Date: Fri, 22 Nov 2024 18:59:51 +0100 Subject: [PATCH 3/8] nft defender --- backend/routes/nft.go | 121 +++++++++++++++++++++++ frontend/public/index.html | 2 +- frontend/src/App.js | 55 ++++++++--- frontend/src/configs/backend.config.json | 2 +- frontend/src/footer/PixelSelector.js | 2 + frontend/src/index.css | 3 +- frontend/src/tabs/nfts/NFTItem.js | 2 +- 7 files changed, 172 insertions(+), 15 deletions(-) diff --git a/backend/routes/nft.go b/backend/routes/nft.go index e27aaefa..5dc11753 100644 --- a/backend/routes/nft.go +++ b/backend/routes/nft.go @@ -1,6 +1,9 @@ package routes import ( + "context" + "encoding/json" + "fmt" "io" "net/http" "os" @@ -19,6 +22,7 @@ func InitNFTRoutes() { http.HandleFunc("/get-new-nfts", getNewNFTs) http.HandleFunc("/get-my-nfts", getMyNFTs) http.HandleFunc("/get-nft-likes", getNftLikeCount) + http.HandleFunc("/get-nft-pixel-data", getNftPixelData) // http.HandleFunc("/like-nft", LikeNFT) // http.HandleFunc("/unlike-nft", UnLikeNFT) http.HandleFunc("/get-top-nfts", getTopNFTs) @@ -220,6 +224,123 @@ func getNewNFTs(w http.ResponseWriter, r *http.Request) { routeutils.WriteDataJson(w, string(nfts)) } +func getNftPixelData(w http.ResponseWriter, r *http.Request) { + tokenId := r.URL.Query().Get("tokenId") + if tokenId == "" { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "TokenId parameter is required") + return + } + + // First get the NFT data to access the imageHash + nft, err := core.PostgresQueryOneJson[NFTData]("SELECT * FROM nfts WHERE token_id = $1", tokenId) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusNotFound, "NFT not found") + return + } + + var nftData NFTData + if err := json.Unmarshal([]byte(nft), &nftData); err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to parse NFT data") + return + } + + // Get round number from environment + roundNumber := os.Getenv("ROUND_NUMBER") + if roundNumber == "" { + roundNumber = "1" // Default to round 1 if not set + } + + // Try to read from file first + filename := fmt.Sprintf("./nfts/round-%s/images/nft-%s.png", roundNumber, tokenId) + fileBytes, err := os.ReadFile(filename) + if err != nil { + filename = fmt.Sprintf("nfts/round-%s/images/nft-%s.png", roundNumber, tokenId) + fileBytes, err = os.ReadFile(filename) + if err != nil { + // If file not found, try to get pixel data from canvas using imageHash + ctx := context.Background() + canvas, err := core.ArtPeaceBackend.Databases.Redis.Get(ctx, "canvas").Result() + if err != nil { + routeutils.WriteErrorJson(w, http.StatusNotFound, "NFT image not found and canvas data unavailable") + return + } + + // Create response using NFT dimensions from database + response := struct { + Width int `json:"width"` + Height int `json:"height"` + PixelData []int `json:"pixelData"` + }{ + Width: nftData.Width, + Height: nftData.Height, + PixelData: make([]int, nftData.Width*nftData.Height), + } + + // Extract pixel data from canvas using position and dimensions + bitWidth := core.ArtPeaceBackend.CanvasConfig.ColorsBitWidth + canvasWidth := int(core.ArtPeaceBackend.CanvasConfig.Canvas.Width) + + for y := 0; y < nftData.Height; y++ { + for x := 0; x < nftData.Width; x++ { + pos := nftData.Position + x + (y * canvasWidth) + bitPos := uint(pos) * bitWidth + bytePos := int(bitPos / 8) // Convert to int here + bitOffset := uint(bitPos % 8) // Keep as uint for bit operations + + if bytePos >= len(canvas) { + continue + } + + var colorIdx int + if bitOffset <= 3 { // Single byte case + colorIdx = int((canvas[bytePos] >> (8 - bitWidth - bitOffset)) & ((1 << bitWidth) - 1)) + } else { // Two byte case + if bytePos+1 >= len(canvas) { + continue + } + colorIdx = int(((uint16(canvas[bytePos])<<8)|uint16(canvas[bytePos+1]))>>(16-bitWidth-bitOffset)) & ((1 << bitWidth) - 1) + } + response.PixelData[x+y*nftData.Width] = colorIdx + } + } + + jsonResponse, err := json.Marshal(response) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to create response") + return + } + + routeutils.WriteDataJson(w, string(jsonResponse)) + return + } + } + + // If we have the file, process it normally + pixelData, err := imageToPixelData(fileBytes) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to process image") + return + } + + response := struct { + Width int `json:"width"` + Height int `json:"height"` + PixelData []int `json:"pixelData"` + }{ + Width: nftData.Width, + Height: nftData.Height, + PixelData: pixelData, + } + + jsonResponse, err := json.Marshal(response) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to create response") + return + } + + routeutils.WriteDataJson(w, string(jsonResponse)) +} + func mintNFTDevnet(w http.ResponseWriter, r *http.Request) { // Disable this in production if routeutils.NonProductionMiddleware(w, r) { diff --git a/frontend/public/index.html b/frontend/public/index.html index 7022acd6..ed2e976b 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -1,4 +1,4 @@ - + diff --git a/frontend/src/App.js b/frontend/src/App.js index 44bb6aac..3130fa49 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1009,20 +1009,53 @@ function App() { return []; }; - // Only call getTemplatePixelData if overlayTemplate exists and has a hash - if (overlayTemplate && overlayTemplate.hash) { - // Need to handle the Promise properly - getTemplatePixelData(overlayTemplate.hash) - .then((data) => setTemplatePixels(data)) - .catch((error) => { - console.error('Error fetching template pixels:', error); + const getNftPixelData = async (tokenId) => { + if (tokenId !== null) { + const response = await fetchWrapper( + `get-nft-pixel-data?tokenId=${tokenId}` + ); + if (!response.data) { + console.error('NFT pixel data not found'); + return []; + } + return response.data; + } + return []; + }; + + const fetchPixelData = async () => { + try { + if (!overlayTemplate) { setTemplatePixels([]); - }); - } else { - setTemplatePixels([]); - } + return; + } + + // Handle NFT overlay case + if (overlayTemplate.isNft && overlayTemplate.tokenId !== undefined) { + const data = await getNftPixelData(overlayTemplate.tokenId); + setTemplatePixels(data); + return; + } + + // Handle template overlay case + if (overlayTemplate.hash) { + const data = await getTemplatePixelData(overlayTemplate.hash); + setTemplatePixels(data); + return; + } + + setTemplatePixels([]); + } catch (error) { + console.error('Error fetching pixel data:', error); + setTemplatePixels([]); + } + }; + + fetchPixelData(); }, [overlayTemplate]); + console.log('overlay template pixels: ', templatePixels); + return (
diff --git a/frontend/src/configs/backend.config.json b/frontend/src/configs/backend.config.json index e9f57c37..c56f3c4d 100644 --- a/frontend/src/configs/backend.config.json +++ b/frontend/src/configs/backend.config.json @@ -1,6 +1,6 @@ { "host": "api.art-peace.net", - "port": 8080, + "port": 8081, "scripts": { "place_pixel_devnet": "../tests/integration/local/place_pixel.sh", "place_extra_pixels_devnet": "../tests/integration/local/place_extra_pixels.sh", diff --git a/frontend/src/footer/PixelSelector.js b/frontend/src/footer/PixelSelector.js index 5f58e8f4..0dd641a5 100644 --- a/frontend/src/footer/PixelSelector.js +++ b/frontend/src/footer/PixelSelector.js @@ -170,6 +170,8 @@ const PixelSelector = (props) => { setEnded(false); }; + console.log(props.templatePixels.pixelData); + return (
{(props.selectorMode || ended) && ( diff --git a/frontend/src/index.css b/frontend/src/index.css index f08406a5..7a3e9cc3 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,6 +1,7 @@ @font-face { font-family: 'Public-Pixel'; - src: local('Public-Pixel'), + src: + local('Public-Pixel'), url('./fonts/PublicPixel-z84yD.ttf') format('truetype'); } diff --git a/frontend/src/tabs/nfts/NFTItem.js b/frontend/src/tabs/nfts/NFTItem.js index f956f215..aefca25a 100644 --- a/frontend/src/tabs/nfts/NFTItem.js +++ b/frontend/src/tabs/nfts/NFTItem.js @@ -159,7 +159,7 @@ const NFTItem = (props) => { width: props.width, height: props.height, image: props.image, - isNft: true, // Add flag to identify as NFT + isNft: true, tokenId: props.tokenId }; props.setTemplateOverlayMode(true); From 26be1f9fc2112e0bd99cee0c0d6150db0f457300 Mon Sep 17 00:00:00 2001 From: Supreme2580 Date: Fri, 22 Nov 2024 20:05:18 +0100 Subject: [PATCH 4/8] nft defender --- backend/routes/nft.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/routes/nft.go b/backend/routes/nft.go index 5dc11753..498a1b61 100644 --- a/backend/routes/nft.go +++ b/backend/routes/nft.go @@ -284,18 +284,20 @@ func getNftPixelData(w http.ResponseWriter, r *http.Request) { for x := 0; x < nftData.Width; x++ { pos := nftData.Position + x + (y * canvasWidth) bitPos := uint(pos) * bitWidth - bytePos := int(bitPos / 8) // Convert to int here - bitOffset := uint(bitPos % 8) // Keep as uint for bit operations + bytePos := int(bitPos / 8) + bitOffset := uint(bitPos % 8) if bytePos >= len(canvas) { + response.PixelData[x+y*nftData.Width] = 0xFF continue } var colorIdx int - if bitOffset <= 3 { // Single byte case + if bitOffset <= 3 { colorIdx = int((canvas[bytePos] >> (8 - bitWidth - bitOffset)) & ((1 << bitWidth) - 1)) - } else { // Two byte case + } else { if bytePos+1 >= len(canvas) { + response.PixelData[x+y*nftData.Width] = 0xFF continue } colorIdx = int(((uint16(canvas[bytePos])<<8)|uint16(canvas[bytePos+1]))>>(16-bitWidth-bitOffset)) & ((1 << bitWidth) - 1) From 9c2678d52e9267999c9f6bfc9bd67c9243498938 Mon Sep 17 00:00:00 2001 From: Supreme2580 Date: Fri, 22 Nov 2024 20:06:47 +0100 Subject: [PATCH 5/8] nft defender --- frontend/src/App.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/App.js b/frontend/src/App.js index 3130fa49..1f67a758 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1054,8 +1054,6 @@ function App() { fetchPixelData(); }, [overlayTemplate]); - console.log('overlay template pixels: ', templatePixels); - return (
From 112aa21b556c281073c4b0819d951bd51cfb1c23 Mon Sep 17 00:00:00 2001 From: Supreme2580 Date: Fri, 22 Nov 2024 23:28:53 +0100 Subject: [PATCH 6/8] nft defender --- backend/routes/nft.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/backend/routes/nft.go b/backend/routes/nft.go index 498a1b61..9fcb58bc 100644 --- a/backend/routes/nft.go +++ b/backend/routes/nft.go @@ -257,7 +257,14 @@ func getNftPixelData(w http.ResponseWriter, r *http.Request) { filename = fmt.Sprintf("nfts/round-%s/images/nft-%s.png", roundNumber, tokenId) fileBytes, err = os.ReadFile(filename) if err != nil { - // If file not found, try to get pixel data from canvas using imageHash + // If file not found, get colors from postgres for canvas fallback + colors, err := core.PostgresQuery[string]("SELECT hex FROM colors ORDER BY key") + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to get color palette") + return + } + + // Get canvas data ctx := context.Background() canvas, err := core.ArtPeaceBackend.Databases.Redis.Get(ctx, "canvas").Result() if err != nil { @@ -265,7 +272,7 @@ func getNftPixelData(w http.ResponseWriter, r *http.Request) { return } - // Create response using NFT dimensions from database + // Create response using NFT dimensions response := struct { Width int `json:"width"` Height int `json:"height"` @@ -276,10 +283,10 @@ func getNftPixelData(w http.ResponseWriter, r *http.Request) { PixelData: make([]int, nftData.Width*nftData.Height), } - // Extract pixel data from canvas using position and dimensions bitWidth := core.ArtPeaceBackend.CanvasConfig.ColorsBitWidth canvasWidth := int(core.ArtPeaceBackend.CanvasConfig.Canvas.Width) + // Extract pixel data from canvas for y := 0; y < nftData.Height; y++ { for x := 0; x < nftData.Width; x++ { pos := nftData.Position + x + (y * canvasWidth) @@ -302,7 +309,12 @@ func getNftPixelData(w http.ResponseWriter, r *http.Request) { } colorIdx = int(((uint16(canvas[bytePos])<<8)|uint16(canvas[bytePos+1]))>>(16-bitWidth-bitOffset)) & ((1 << bitWidth) - 1) } - response.PixelData[x+y*nftData.Width] = colorIdx + + if colorIdx >= len(colors) { + response.PixelData[x+y*nftData.Width] = 0xFF + } else { + response.PixelData[x+y*nftData.Width] = colorIdx + } } } @@ -317,7 +329,7 @@ func getNftPixelData(w http.ResponseWriter, r *http.Request) { } } - // If we have the file, process it normally + // If we have the file, process it using the template's imageToPixelData function pixelData, err := imageToPixelData(fileBytes) if err != nil { routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to process image") From 04ff04c673d14d5f342367aa1e15bbdaf2a03237 Mon Sep 17 00:00:00 2001 From: Supreme2580 Date: Fri, 22 Nov 2024 23:50:52 +0100 Subject: [PATCH 7/8] nft defender --- backend/routes/nft.go | 23 ++++++++++++----------- frontend/src/footer/PixelSelector.js | 2 -- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/backend/routes/nft.go b/backend/routes/nft.go index 9fcb58bc..1c9b90ee 100644 --- a/backend/routes/nft.go +++ b/backend/routes/nft.go @@ -244,27 +244,26 @@ func getNftPixelData(w http.ResponseWriter, r *http.Request) { return } - // Get round number from environment + // Get colors from postgres for consistent palette + colors, err := core.PostgresQuery[string]("SELECT hex FROM colors ORDER BY key") + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to get color palette") + return + } + + // Try to read from file first roundNumber := os.Getenv("ROUND_NUMBER") if roundNumber == "" { roundNumber = "1" // Default to round 1 if not set } - // Try to read from file first filename := fmt.Sprintf("./nfts/round-%s/images/nft-%s.png", roundNumber, tokenId) fileBytes, err := os.ReadFile(filename) if err != nil { filename = fmt.Sprintf("nfts/round-%s/images/nft-%s.png", roundNumber, tokenId) fileBytes, err = os.ReadFile(filename) if err != nil { - // If file not found, get colors from postgres for canvas fallback - colors, err := core.PostgresQuery[string]("SELECT hex FROM colors ORDER BY key") - if err != nil { - routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to get color palette") - return - } - - // Get canvas data + // If file not found, fallback to canvas data ctx := context.Background() canvas, err := core.ArtPeaceBackend.Databases.Redis.Get(ctx, "canvas").Result() if err != nil { @@ -294,6 +293,7 @@ func getNftPixelData(w http.ResponseWriter, r *http.Request) { bytePos := int(bitPos / 8) bitOffset := uint(bitPos % 8) + // Handle out of bounds as transparent (0xFF) if bytePos >= len(canvas) { response.PixelData[x+y*nftData.Width] = 0xFF continue @@ -310,6 +310,7 @@ func getNftPixelData(w http.ResponseWriter, r *http.Request) { colorIdx = int(((uint16(canvas[bytePos])<<8)|uint16(canvas[bytePos+1]))>>(16-bitWidth-bitOffset)) & ((1 << bitWidth) - 1) } + // Validate color index if colorIdx >= len(colors) { response.PixelData[x+y*nftData.Width] = 0xFF } else { @@ -329,7 +330,7 @@ func getNftPixelData(w http.ResponseWriter, r *http.Request) { } } - // If we have the file, process it using the template's imageToPixelData function + // If we have the file, process it using imageToPixelData pixelData, err := imageToPixelData(fileBytes) if err != nil { routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to process image") diff --git a/frontend/src/footer/PixelSelector.js b/frontend/src/footer/PixelSelector.js index 0dd641a5..5f58e8f4 100644 --- a/frontend/src/footer/PixelSelector.js +++ b/frontend/src/footer/PixelSelector.js @@ -170,8 +170,6 @@ const PixelSelector = (props) => { setEnded(false); }; - console.log(props.templatePixels.pixelData); - return (
{(props.selectorMode || ended) && ( From 7498f5bf9970ca370c9e649449e1f6374f3c9934 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Mon, 25 Nov 2024 10:16:26 -0600 Subject: [PATCH 8/8] Misc changes and docker nft volume --- backend/routes/nft.go | 82 ++---------------------- backend/routes/templates.go | 24 ++++--- docker-compose.yml | 2 + frontend/src/configs/backend.config.json | 3 +- frontend/src/tabs/nfts/NFTItem.css | 26 ++++++++ onchain/src/art_peace.cairo | 2 +- 6 files changed, 49 insertions(+), 90 deletions(-) diff --git a/backend/routes/nft.go b/backend/routes/nft.go index 1c9b90ee..de6f7762 100644 --- a/backend/routes/nft.go +++ b/backend/routes/nft.go @@ -1,7 +1,6 @@ package routes import ( - "context" "encoding/json" "fmt" "io" @@ -244,94 +243,21 @@ func getNftPixelData(w http.ResponseWriter, r *http.Request) { return } - // Get colors from postgres for consistent palette - colors, err := core.PostgresQuery[string]("SELECT hex FROM colors ORDER BY key") - if err != nil { - routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to get color palette") - return - } - // Try to read from file first roundNumber := os.Getenv("ROUND_NUMBER") if roundNumber == "" { roundNumber = "1" // Default to round 1 if not set } - filename := fmt.Sprintf("./nfts/round-%s/images/nft-%s.png", roundNumber, tokenId) + filename := fmt.Sprintf("nfts/round-%s/images/nft-%s.png", roundNumber, tokenId) fileBytes, err := os.ReadFile(filename) if err != nil { - filename = fmt.Sprintf("nfts/round-%s/images/nft-%s.png", roundNumber, tokenId) - fileBytes, err = os.ReadFile(filename) - if err != nil { - // If file not found, fallback to canvas data - ctx := context.Background() - canvas, err := core.ArtPeaceBackend.Databases.Redis.Get(ctx, "canvas").Result() - if err != nil { - routeutils.WriteErrorJson(w, http.StatusNotFound, "NFT image not found and canvas data unavailable") - return - } - - // Create response using NFT dimensions - response := struct { - Width int `json:"width"` - Height int `json:"height"` - PixelData []int `json:"pixelData"` - }{ - Width: nftData.Width, - Height: nftData.Height, - PixelData: make([]int, nftData.Width*nftData.Height), - } - - bitWidth := core.ArtPeaceBackend.CanvasConfig.ColorsBitWidth - canvasWidth := int(core.ArtPeaceBackend.CanvasConfig.Canvas.Width) - - // Extract pixel data from canvas - for y := 0; y < nftData.Height; y++ { - for x := 0; x < nftData.Width; x++ { - pos := nftData.Position + x + (y * canvasWidth) - bitPos := uint(pos) * bitWidth - bytePos := int(bitPos / 8) - bitOffset := uint(bitPos % 8) - - // Handle out of bounds as transparent (0xFF) - if bytePos >= len(canvas) { - response.PixelData[x+y*nftData.Width] = 0xFF - continue - } - - var colorIdx int - if bitOffset <= 3 { - colorIdx = int((canvas[bytePos] >> (8 - bitWidth - bitOffset)) & ((1 << bitWidth) - 1)) - } else { - if bytePos+1 >= len(canvas) { - response.PixelData[x+y*nftData.Width] = 0xFF - continue - } - colorIdx = int(((uint16(canvas[bytePos])<<8)|uint16(canvas[bytePos+1]))>>(16-bitWidth-bitOffset)) & ((1 << bitWidth) - 1) - } - - // Validate color index - if colorIdx >= len(colors) { - response.PixelData[x+y*nftData.Width] = 0xFF - } else { - response.PixelData[x+y*nftData.Width] = colorIdx - } - } - } - - jsonResponse, err := json.Marshal(response) - if err != nil { - routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to create response") - return - } - - routeutils.WriteDataJson(w, string(jsonResponse)) - return - } + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to read image file") + return } // If we have the file, process it using imageToPixelData - pixelData, err := imageToPixelData(fileBytes) + pixelData, err := imageToPixelData(fileBytes, 10) if err != nil { routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to process image") return diff --git a/backend/routes/templates.go b/backend/routes/templates.go index 365c96a6..d66f3a07 100644 --- a/backend/routes/templates.go +++ b/backend/routes/templates.go @@ -76,7 +76,7 @@ func hexToRGBA(colorBytes string) color.RGBA { return color.RGBA{uint8(r), uint8(g), uint8(b), 255} } -func imageToPixelData(imageData []byte) ([]int, error) { +func imageToPixelData(imageData []byte, scaleFactor int) ([]int, error) { img, _, err := image.Decode(bytes.NewReader(imageData)) if err != nil { return nil, err @@ -96,16 +96,20 @@ func imageToPixelData(imageData []byte) ([]int, error) { bounds := img.Bounds() width, height := bounds.Max.X, bounds.Max.Y - pixelData := make([]int, width*height) - - for y := 0; y < height; y++ { - for x := 0; x < width; x++ { + scaledWidth := width / scaleFactor + scaledHeight := height / scaleFactor + pixelData := make([]int, scaledWidth*scaledHeight) + + for y := 0; y < height; y += scaleFactor { + for x := 0; x < width; x += scaleFactor { + newX := x / scaleFactor + newY := y / scaleFactor rgba := color.RGBAModel.Convert(img.At(x, y)).(color.RGBA) if rgba.A < 128 { // Consider pixels with less than 50% opacity as transparent - pixelData[y*width+x] = 0xFF + pixelData[newY*scaledWidth+newX] = 0xFF } else { closestIndex := findClosestColor(rgba, palette) - pixelData[y*width+x] = closestIndex + pixelData[newY*scaledWidth+newX] = closestIndex } } } @@ -229,7 +233,7 @@ func buildTemplateImg(w http.ResponseWriter, r *http.Request) { return } - imageData, err := imageToPixelData(fileBytes) + imageData, err := imageToPixelData(fileBytes, 1) if err != nil { routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to convert image to pixel data") return @@ -314,7 +318,7 @@ func addTemplateImg(w http.ResponseWriter, r *http.Request) { r.Body.Close() - imageData, err := imageToPixelData(fileBytes) + imageData, err := imageToPixelData(fileBytes, 1) if err != nil { routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to convert image to pixel data") return @@ -473,7 +477,7 @@ func getTemplatePixelData(w http.ResponseWriter, r *http.Request) { } // Convert image to pixel data using existing function - pixelData, err := imageToPixelData(fileBytes) + pixelData, err := imageToPixelData(fileBytes, 1) if err != nil { routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to process image") return diff --git a/docker-compose.yml b/docker-compose.yml index 55e12a46..b9bb3ca3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,6 +36,8 @@ services: - ART_PEACE_END_TIME=3000000000 - ART_PEACE_HOST=0328ced46664355fc4b885ae7011af202313056a7e3d44827fb24c9d3206aaa0 - ROUND_NUMBER=1 + volumes: + - nfts:/app/nfts consumer: build: dockerfile: backend/Dockerfile.consumer diff --git a/frontend/src/configs/backend.config.json b/frontend/src/configs/backend.config.json index c56f3c4d..9636245b 100644 --- a/frontend/src/configs/backend.config.json +++ b/frontend/src/configs/backend.config.json @@ -1,6 +1,7 @@ { "host": "api.art-peace.net", - "port": 8081, + "port": 8080, + "consumer_port": 8081, "scripts": { "place_pixel_devnet": "../tests/integration/local/place_pixel.sh", "place_extra_pixels_devnet": "../tests/integration/local/place_extra_pixels.sh", diff --git a/frontend/src/tabs/nfts/NFTItem.css b/frontend/src/tabs/nfts/NFTItem.css index b37446ac..65a5db72 100644 --- a/frontend/src/tabs/nfts/NFTItem.css +++ b/frontend/src/tabs/nfts/NFTItem.css @@ -29,6 +29,32 @@ margin: 0; padding: 0; image-rendering: pixelated; + cursor: pointer; + + transition: all 0.2s; +} + +/* pulse animation */ +@keyframes pulse { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } + 100% { + transform: scale(1); + } +} + +.NFTItem__image:hover { + animation: pulse 1s infinite; + transform: scale(1.03); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.6); +} + +.NFTItem__image:active { + transform: scale(1); } .NFTItem__buttons { diff --git a/onchain/src/art_peace.cairo b/onchain/src/art_peace.cairo index 7bae6e4b..b8afced2 100644 --- a/onchain/src/art_peace.cairo +++ b/onchain/src/art_peace.cairo @@ -381,7 +381,7 @@ pub mod ArtPeace { let test_address = starknet::contract_address_const::< 0x328ced46664355fc4b885ae7011af202313056a7e3d44827fb24c9d3206aaa0 >(); - self.extra_pixels.write(test_address, 1000); + self.extra_pixels.write(test_address, 100000); } self.devmode.write(init_params.devmode);