diff --git a/app/components/templates/panel/dashboard-entity-panel.values.asset.image.tsx b/app/components/templates/panel/dashboard-entity-panel.values.asset.image.tsx
index 8e2901db..f0f26f72 100644
--- a/app/components/templates/panel/dashboard-entity-panel.values.asset.image.tsx
+++ b/app/components/templates/panel/dashboard-entity-panel.values.asset.image.tsx
@@ -4,6 +4,7 @@ import { SidebarPanelPopoverFormContainer } from '#app/components/layout/popover
import { type IAssetImage } from '#app/models/asset/image/image.server'
import { getAssetImgSrc } from '#app/models/asset/image/utils'
import { AssetImageUpdateFit } from '#app/routes/resources+/api.v1+/asset.image.update.fit'
+import { AssetImageUpdateHideOnDraw } from '#app/routes/resources+/api.v1+/asset.image.update.hide-on-draw'
import { AssetUpdateName } from '#app/routes/resources+/api.v1+/asset.update.name'
import { SidebarPanelRowValuesContainer } from '..'
import { PanelEntityPopover } from './dashboard-entity-panel.popover'
@@ -31,6 +32,10 @@ const EntityPopover = memo(({ image }: EntityProps) => {
Fit
+
+ Hide on Draw
+
+
)
})
diff --git a/app/definitions/artwork-generator.ts b/app/definitions/artwork-generator.ts
index a0eff123..83438129 100644
--- a/app/definitions/artwork-generator.ts
+++ b/app/definitions/artwork-generator.ts
@@ -1,8 +1,8 @@
import { type IArtwork } from '#app/models/artwork/artwork.server'
import { type IArtworkBranch } from '#app/models/artwork-branch/artwork-branch.server'
import { type IArtworkVersion } from '#app/models/artwork-version/artwork-version.server'
+import { type IAssetGenerationByType } from '#app/models/asset/asset.generation.server'
import { type IAssetByType } from '#app/models/asset/asset.server'
-import { type IAssetImageGeneration } from '#app/models/asset/image/image.generate.server'
import { type IFill } from '#app/models/design-type/fill/fill.server'
import { type ILayout } from '#app/models/design-type/layout/layout.server'
import { type ILine } from '#app/models/design-type/line/line.server'
@@ -72,6 +72,11 @@ export interface ILayerGenerator extends IGeneratorDesigns {
name?: ILayer['name']
description?: ILayer['description']
+ // layer always has access to the background color
+ // if drawing image to get pixel data
+ // this allows the background to be redrawn
+ background: string
+
// create this design type
container: ILayerGeneratorContainer
@@ -94,14 +99,10 @@ export interface ILayerGeneratorContainer {
export interface IGenerationLayer {
generator: ILayerGenerator
- assets: IGenerationAssets
+ assets: IAssetGenerationByType
items: IGenerationItem[]
}
-export interface IGenerationAssets {
- image: IAssetImageGeneration | null
-}
-
export interface IGenerationItem {
id: string
fillStyle: string
diff --git a/app/models/asset/asset.generation.server.ts b/app/models/asset/asset.generation.server.ts
new file mode 100644
index 00000000..45f5d750
--- /dev/null
+++ b/app/models/asset/asset.generation.server.ts
@@ -0,0 +1,5 @@
+import { type IAssetImageGeneration } from './image/image.generate.server'
+
+export interface IAssetGenerationByType {
+ assetImages: IAssetImageGeneration[]
+}
diff --git a/app/models/asset/image/image.generate.server.ts b/app/models/asset/image/image.generate.server.ts
index d3344edf..e1f3fcb2 100644
--- a/app/models/asset/image/image.generate.server.ts
+++ b/app/models/asset/image/image.generate.server.ts
@@ -1,3 +1,10 @@
+import { type IAssetImage } from './image.server'
+
+export interface IAssetImageSrcGeneration {
+ id: IAssetImage['id']
+ src: string
+}
+
export interface IAssetImageDrawGeneration {
x: number
y: number
@@ -5,6 +12,8 @@ export interface IAssetImageDrawGeneration {
height: number
}
-export interface IAssetImageGeneration extends IAssetImageDrawGeneration {
- src: string
+export interface IAssetImageGeneration
+ extends IAssetImageSrcGeneration,
+ IAssetImageDrawGeneration {
+ hideOnDraw: boolean
}
diff --git a/app/models/asset/image/image.server.ts b/app/models/asset/image/image.server.ts
index 5f454198..7d53debd 100644
--- a/app/models/asset/image/image.server.ts
+++ b/app/models/asset/image/image.server.ts
@@ -24,6 +24,7 @@ export interface IAssetImageFileData {
lastModified?: number
filename: string
fit?: IAssetImageFit
+ hideOnDraw?: boolean
}
// when adding attributes to an asset type,
diff --git a/app/models/asset/image/image.update.hide-on-draw.server.ts b/app/models/asset/image/image.update.hide-on-draw.server.ts
new file mode 100644
index 00000000..16a33cc7
--- /dev/null
+++ b/app/models/asset/image/image.update.hide-on-draw.server.ts
@@ -0,0 +1,49 @@
+import { type IntentActionArgs } from '#app/definitions/intent-action-args'
+import { type IUser } from '#app/models/user/user.server'
+import { EditAssetImageHideOnDrawSchema } from '#app/schema/asset/image'
+import { ValidateAssetSubmissionStrategy } from '#app/strategies/validate-submission.strategy'
+import { validateEntitySubmission } from '#app/utils/conform-utils'
+import { prisma } from '#app/utils/db.server'
+import { type IAssetImage, type IAssetImageFileData } from './image.server'
+import { stringifyAssetImageAttributes } from './utils'
+
+export const validateEditHideOnDrawAssetImageSubmission = async ({
+ userId,
+ formData,
+}: IntentActionArgs) => {
+ const strategy = new ValidateAssetSubmissionStrategy()
+
+ return await validateEntitySubmission({
+ userId,
+ formData,
+ schema: EditAssetImageHideOnDrawSchema,
+ strategy,
+ })
+}
+
+export interface IAssetImageUpdateHideOnDrawSubmission {
+ userId: IUser['id']
+ id: IAssetImage['id']
+ hideOnDraw: boolean
+}
+
+interface IAssetImageUpdateHideOnDrawData {
+ attributes: IAssetImageFileData
+}
+
+export const updateAssetImageHideOnDraw = ({
+ id,
+ data,
+}: {
+ id: IAssetImage['id']
+ data: IAssetImageUpdateHideOnDrawData
+}) => {
+ const { attributes } = data
+ const jsonAttributes = stringifyAssetImageAttributes(attributes)
+ return prisma.asset.update({
+ where: { id },
+ data: {
+ attributes: jsonAttributes,
+ },
+ })
+}
diff --git a/app/models/asset/utils.ts b/app/models/asset/utils.ts
index be3cebd0..450567ad 100644
--- a/app/models/asset/utils.ts
+++ b/app/models/asset/utils.ts
@@ -65,6 +65,14 @@ export const validateAssetAttributes = ({
}
}
+export const filterAssetsVisible = ({
+ assets,
+}: {
+ assets: IAssetParsed[]
+}): IAssetType[] => {
+ return assets.filter(asset => asset.visible)
+}
+
export const filterAssetType = ({
assets,
type,
diff --git a/app/routes/resources+/api.v1+/asset.image.update.hide-on-draw.tsx b/app/routes/resources+/api.v1+/asset.image.update.hide-on-draw.tsx
new file mode 100644
index 00000000..498b0609
--- /dev/null
+++ b/app/routes/resources+/api.v1+/asset.image.update.hide-on-draw.tsx
@@ -0,0 +1,101 @@
+import {
+ json,
+ type ActionFunctionArgs,
+ type LoaderFunctionArgs,
+} from '@remix-run/node'
+import { useFetcher } from '@remix-run/react'
+import { redirectBack } from 'remix-utils/redirect-back'
+import { useHydrated } from 'remix-utils/use-hydrated'
+import { FetcherIconButton } from '#app/components/templates/form/fetcher-icon-button'
+import { type IAssetImage } from '#app/models/asset/image/image.server'
+import { validateEditHideOnDrawAssetImageSubmission } from '#app/models/asset/image/image.update.hide-on-draw.server'
+import { EditAssetImageHideOnDrawSchema } from '#app/schema/asset/image'
+import { validateNoJS } from '#app/schema/form-data'
+import { assetImageUpdateHideOnDrawService } from '#app/services/asset.image.update.hide-on-draw.service'
+import { requireUserId } from '#app/utils/auth.server'
+import { Routes } from '#app/utils/routes.const'
+
+// https://www.epicweb.dev/full-stack-components
+
+const route = Routes.RESOURCES.API.V1.ASSET.IMAGE.UPDATE.HIDE_ON_DRAW
+const schema = EditAssetImageHideOnDrawSchema
+
+// auth GET request to endpoint
+export async function loader({ request }: LoaderFunctionArgs) {
+ await requireUserId(request)
+ return json({})
+}
+
+export async function action({ request }: ActionFunctionArgs) {
+ const userId = await requireUserId(request)
+ const formData = await request.formData()
+ const noJS = validateNoJS({ formData })
+
+ let updateSuccess = false
+ let errorMessage = ''
+ const { status, submission } =
+ await validateEditHideOnDrawAssetImageSubmission({
+ userId,
+ formData,
+ })
+
+ if (status === 'success') {
+ const { success, message } = await assetImageUpdateHideOnDrawService({
+ userId,
+ ...submission.value,
+ })
+ updateSuccess = success
+ errorMessage = message || ''
+ }
+
+ if (noJS) {
+ throw redirectBack(request, {
+ fallback: '/',
+ })
+ }
+
+ return json(
+ { status, submission, message: errorMessage },
+ {
+ status: status === 'error' || !updateSuccess ? 404 : 200,
+ },
+ )
+}
+
+export const AssetImageUpdateHideOnDraw = ({
+ image,
+ formLocation = '',
+}: {
+ image: IAssetImage
+ formLocation?: string
+}) => {
+ const assetId = image.id
+ const field = 'hideOnDraw'
+ const isHiddenOnDraw = image.attributes[field] || false
+ const icon = isHiddenOnDraw ? 'eye-none' : 'eye-open'
+ const iconText = `${isHiddenOnDraw ? 'Show' : 'Show'} on draw`
+ const fetcherKey = `asset-image-update-${field}-${assetId}`
+ const formId = `${fetcherKey}${formLocation ? `-${formLocation}` : ''}`
+
+ let isHydrated = useHydrated()
+ const fetcher = useFetcher({
+ key: fetcherKey,
+ })
+
+ return (
+
+
+
+
+
+ )
+}
diff --git a/app/schema/asset/image.ts b/app/schema/asset/image.ts
index f4f98d60..8d57f801 100644
--- a/app/schema/asset/image.ts
+++ b/app/schema/asset/image.ts
@@ -61,6 +61,7 @@ export const AssetAttributesImageSchema = z.object({
lastModified: z.number().optional(),
filename: z.string(),
fit: z.nativeEnum(AssetImageFitTypeEnum).optional(),
+ hideOnDraw: z.boolean().optional(),
})
// zod schema for blob Buffer/File is not working
@@ -94,6 +95,10 @@ export const EditAssetImageFitSchema = z.object({
fit: z.nativeEnum(AssetImageFitTypeEnum),
})
+export const EditAssetImageHideOnDrawSchema = z.object({
+ id: z.string(),
+})
+
export const DeleteAssetImageSchema = z.object({
id: z.string(),
})
diff --git a/app/services/artwork/version/generator/build.container.service.ts b/app/services/artwork/version/generator/build.container.service.ts
new file mode 100644
index 00000000..77dbb630
--- /dev/null
+++ b/app/services/artwork/version/generator/build.container.service.ts
@@ -0,0 +1,20 @@
+import { type IArtworkVersionWithChildren } from '#app/models/artwork-version/artwork-version.server'
+
+export const getArtworkVersionContainer = ({
+ version,
+}: {
+ version: IArtworkVersionWithChildren
+}) => {
+ const { width, height } = version
+ return {
+ width,
+ height,
+ top: 0,
+ left: 0,
+ margin: 0,
+ canvas: {
+ width,
+ height,
+ },
+ }
+}
diff --git a/app/services/artwork/version/generator/build.layers.service.ts b/app/services/artwork/version/generator/build.layers.service.ts
new file mode 100644
index 00000000..f0b26dd2
--- /dev/null
+++ b/app/services/artwork/version/generator/build.layers.service.ts
@@ -0,0 +1,156 @@
+import {
+ type IGeneratorDesigns,
+ type ILayerGenerator,
+} from '#app/definitions/artwork-generator'
+import { type IArtworkVersionWithChildren } from '#app/models/artwork-version/artwork-version.server'
+import { filterAssetsVisible, groupAssetsByType } from '#app/models/asset/utils'
+import { findManyDesignsWithType } from '#app/models/design/design.server'
+import { getArtworkVersionVisiblePalettes } from '#app/models/design-artwork-version/design-artwork-version.server'
+import { getLayerVisiblePalettes } from '#app/models/design-layer/design-layer.server'
+import { type ILayerWithChildren } from '#app/models/layer/layer.server'
+import {
+ filterSelectedDesignTypes,
+ findFirstDesignsByTypeInArray,
+} from '#app/utils/design'
+import { filterLayersVisible } from '#app/utils/layer.utils'
+import { orderLinkedItems } from '#app/utils/linked-list.utils'
+import { getArtworkVersionContainer } from './build.container.service'
+import { getRotates } from './build.rotates.service'
+
+// default/global design settings for each layer
+// layer can override any of these values
+export const buildDefaultGeneratorLayer = async ({
+ version,
+ defaultGeneratorDesigns,
+}: {
+ version: IArtworkVersionWithChildren
+ defaultGeneratorDesigns: IGeneratorDesigns
+}): Promise => {
+ const artworkVersionId = version.id
+
+ // get all visible palettes to use for fill or stroke
+ const palettes = await getArtworkVersionVisiblePalettes({
+ artworkVersionId,
+ })
+
+ // get all visible rotates to use for rotate if visible random
+ const rotates = await getRotates({
+ artworkVersionId,
+ rotate: defaultGeneratorDesigns.rotate,
+ })
+
+ // container defaults to version dimensions
+ const container = getArtworkVersionContainer({ version })
+
+ const assets = groupAssetsByType({ assets: version.assets })
+
+ return {
+ ...defaultGeneratorDesigns,
+ background: version.background,
+ palette: palettes,
+ rotates,
+ container,
+ assets,
+ }
+}
+
+export const buildGeneratorLayers = async ({
+ version,
+ defaultGeneratorLayer,
+}: {
+ version: IArtworkVersionWithChildren
+ defaultGeneratorLayer: ILayerGenerator
+}) => {
+ const orderedLayers = await orderLinkedItems(
+ version.layers,
+ )
+ const visibleLayers = filterLayersVisible({
+ layers: orderedLayers,
+ }) as ILayerWithChildren[]
+
+ return await Promise.all(
+ visibleLayers.map(layer =>
+ buildGeneratorLayer({
+ layer,
+ defaultGeneratorLayer,
+ }),
+ ),
+ )
+}
+
+const buildGeneratorLayer = async ({
+ layer,
+ defaultGeneratorLayer,
+}: {
+ layer: ILayerWithChildren
+ defaultGeneratorLayer: ILayerGenerator
+}): Promise => {
+ const layerId = layer.id
+
+ // Step 1: get all selected designs for the layer
+ const layerSelectedDesigns = await getLayerSelectedDesigns({ layerId })
+
+ // Step 2: split the selected designs into the first of each type
+ const selectedDesignTypes = findFirstDesignsByTypeInArray({
+ designs: layerSelectedDesigns,
+ })
+
+ // Step 3: filter the selected designs that are present
+ // separate the palette from the rest of the layer generator designs
+ // if the layer has no palette we do not want to override the default palette
+ const { palette, ...layerGeneratorDesigns } = filterSelectedDesignTypes({
+ selectedDesignTypes,
+ })
+
+ // Step 4: initialize the generator layer
+ // with the default generator layer
+ // and the layer generator designs as overrides
+ // and layer details
+ const { id, name, description } = layer
+
+ const assets = groupAssetsByType({
+ assets: filterAssetsVisible({ assets: layer.assets }),
+ })
+
+ const layerGenerator = {
+ ...defaultGeneratorLayer,
+ ...layerGeneratorDesigns,
+ id,
+ name,
+ description,
+ assets,
+ }
+
+ // Step 5: get all visible palettes to use for fill or stroke
+ // if empty, then use the default palette
+ const palettes = await getLayerVisiblePalettes({ layerId })
+ if (palettes.length > 0) {
+ layerGenerator.palette = palettes
+ }
+
+ // Step 6: get all visible rotates to use for rotate if visible random
+ // if empty, then use the default rotate
+ const { rotate } = layerGeneratorDesigns
+ if (rotate) {
+ const rotates = await getRotates({
+ layerId,
+ rotate,
+ })
+
+ if (rotates.length > 0) {
+ layerGenerator.rotates = rotates
+ }
+ }
+
+ return layerGenerator
+}
+
+const getLayerSelectedDesigns = async ({
+ layerId,
+}: {
+ layerId: ILayerWithChildren['id']
+}) => {
+ return await findManyDesignsWithType({
+ where: { layerId, selected: true },
+ })
+}
diff --git a/app/services/artwork/version/generator/build.metadata.service.ts b/app/services/artwork/version/generator/build.metadata.service.ts
new file mode 100644
index 00000000..001d9a31
--- /dev/null
+++ b/app/services/artwork/version/generator/build.metadata.service.ts
@@ -0,0 +1,77 @@
+import { invariant } from '@epic-web/invariant'
+import { type IArtworkVersionGeneratorMetadata } from '#app/definitions/artwork-generator'
+import { type IArtworkVersionWithChildren } from '#app/models/artwork-version/artwork-version.server'
+import { prisma } from '#app/utils/db.server'
+
+export const buildGeneratorMetadata = async ({
+ version,
+}: {
+ version: IArtworkVersionWithChildren
+}): Promise => {
+ const branch = await prisma.artworkBranch.findUnique({
+ where: { id: version.branchId },
+ select: {
+ id: true,
+ name: true,
+ slug: true,
+ description: true,
+ artwork: {
+ select: {
+ id: true,
+ name: true,
+ slug: true,
+ description: true,
+ project: {
+ select: {
+ id: true,
+ name: true,
+ slug: true,
+ description: true,
+ },
+ },
+ owner: {
+ select: {
+ id: true,
+ name: true,
+ username: true,
+ },
+ },
+ },
+ },
+ },
+ })
+ invariant(branch, `Branch not found for version ${version.id}`)
+ const artwork = branch.artwork
+ invariant(artwork, `Artwork not found for branch ${branch.id}`)
+ const project = artwork.project
+ invariant(project, `Project not found for artwork ${artwork.id}`)
+ const owner = artwork.owner
+ invariant(owner, `Owner not found for artwork ${artwork.id}`)
+
+ return {
+ // version
+ versionId: version.id,
+ versionName: version.name,
+ versionSlug: version.slug,
+ versionDescription: version.description,
+ // branch
+ branchId: version.branchId,
+ branchName: branch.name,
+ branchSlug: branch.slug,
+ branchDescription: branch.description,
+ // artwork
+ artworkId: artwork.id,
+ artworkName: artwork.name,
+ artworkSlug: artwork.slug,
+ artworkDescription: artwork.description,
+ // project
+ projectId: project.id,
+ projectName: project.name,
+ projectSlug: project.slug,
+ projectDescription: project.description,
+ // owner
+ ownerId: owner.id,
+ ownerName: owner.name,
+ ownerUsername: owner.username,
+ }
+}
diff --git a/app/services/artwork/version/generator/build.rotates.service.ts b/app/services/artwork/version/generator/build.rotates.service.ts
new file mode 100644
index 00000000..bc24083f
--- /dev/null
+++ b/app/services/artwork/version/generator/build.rotates.service.ts
@@ -0,0 +1,28 @@
+import { type IArtworkVersionWithChildren } from '#app/models/artwork-version/artwork-version.server'
+import { getArtworkVersionVisibleRotates } from '#app/models/design-artwork-version/design-artwork-version.server'
+import { getLayerVisibleRotates } from '#app/models/design-layer/design-layer.server'
+import { type IRotate } from '#app/models/design-type/rotate/rotate.server'
+import { type ILayerWithChildren } from '#app/models/layer/layer.server'
+import { type rotateBasisTypeEnum } from '#app/schema/rotate'
+import { isArrayRotateBasisType } from '#app/utils/rotate'
+
+export const getRotates = async ({
+ artworkVersionId,
+ layerId,
+ rotate,
+}: {
+ artworkVersionId?: IArtworkVersionWithChildren['id']
+ layerId?: ILayerWithChildren['id']
+ rotate: IRotate
+}) => {
+ const allRotates = isArrayRotateBasisType(rotate.basis as rotateBasisTypeEnum)
+
+ if (allRotates) {
+ if (artworkVersionId) {
+ return await getArtworkVersionVisibleRotates({ artworkVersionId })
+ } else if (layerId) {
+ return await getLayerVisibleRotates({ layerId })
+ }
+ }
+ return []
+}
diff --git a/app/services/artwork/version/generator/build.service.ts b/app/services/artwork/version/generator/build.service.ts
index 26ae44f3..b223ef0b 100644
--- a/app/services/artwork/version/generator/build.service.ts
+++ b/app/services/artwork/version/generator/build.service.ts
@@ -1,37 +1,12 @@
-import { invariant } from '@epic-web/invariant'
-import {
- type IGeneratorDesigns,
- type ILayerGenerator,
- type IArtworkVersionGenerator,
- type IGeneratorWatermark,
- type IArtworkVersionGeneratorMetadata,
-} from '#app/definitions/artwork-generator'
+import { type IArtworkVersionGenerator } from '#app/definitions/artwork-generator'
import { type IArtworkVersionWithChildren } from '#app/models/artwork-version/artwork-version.server'
-import { groupAssetsByType } from '#app/models/asset/utils'
-import {
- findManyDesignsWithType,
- type IDesignWithType,
-} from '#app/models/design/design.server'
-import {
- getArtworkVersionVisiblePalettes,
- getArtworkVersionVisibleRotates,
-} from '#app/models/design-artwork-version/design-artwork-version.server'
import {
- getLayerVisiblePalettes,
- getLayerVisibleRotates,
-} from '#app/models/design-layer/design-layer.server'
-import { type IRotate } from '#app/models/design-type/rotate/rotate.server'
-import { type ILayerWithChildren } from '#app/models/layer/layer.server'
-import { type rotateBasisTypeEnum } from '#app/schema/rotate'
-import { prisma } from '#app/utils/db.server'
-import {
- filterSelectedDesignTypes,
- findFirstDesignsByTypeInArray,
- verifySelectedDesignTypesAllPresent,
-} from '#app/utils/design'
-import { filterLayersVisible } from '#app/utils/layer.utils'
-import { orderLinkedItems } from '#app/utils/linked-list.utils'
-import { isArrayRotateBasisType } from '#app/utils/rotate'
+ buildDefaultGeneratorLayer,
+ buildGeneratorLayers,
+} from './build.layers.service'
+import { buildGeneratorMetadata } from './build.metadata.service'
+import { verifyDefaultGeneratorDesigns } from './build.verify.service'
+import { buildGeneratorWatermark } from './build.watermark.service'
// "build" since it is creating the version generator each time
// later, if we like the generator, we can save it to the database
@@ -71,11 +46,8 @@ export const artworkVersionGeneratorBuildService = async ({
// Step 4: build the generator layers
// each layer can override any of the global settings
- const orderedLayers = await orderLinkedItems(
- version.layers,
- )
- const generatorLayers = await buildGeneratorLayers({
- layers: orderedLayers,
+ const layers = await buildGeneratorLayers({
+ version,
defaultGeneratorLayer,
})
@@ -92,7 +64,7 @@ export const artworkVersionGeneratorBuildService = async ({
height: version.height,
background: version.background,
},
- layers: generatorLayers,
+ layers,
watermark,
metadata,
success: true,
@@ -113,330 +85,3 @@ export const artworkVersionGeneratorBuildService = async ({
}
}
}
-
-const verifyDefaultGeneratorDesigns = async ({
- version,
-}: {
- version: IArtworkVersionWithChildren
-}): Promise<{
- defaultGeneratorDesigns: IGeneratorDesigns | null
- message: string
-}> => {
- // Step 1: get all selected designs for the version
- // design table has `selected: boolean` field
- const selectedDesigns = await getVersionSelectedDesigns({
- artworkVersionId: version.id,
- })
-
- // Step 2: split the selected designs into the first of each type
- const selectedDesignTypes = findFirstDesignsByTypeInArray({
- designs: selectedDesigns,
- })
-
- // Step 3: validate that all selected design types are present
- // message will indicate which design type is missing
- const { success, message } = verifySelectedDesignTypesAllPresent({
- selectedDesignTypes,
- })
-
- // Step 4: return failure with message if selected designs are not valid
- if (!success) {
- return {
- message,
- defaultGeneratorDesigns: null,
- }
- }
-
- // Step 5: reformat the selected designs to be generator designs
- // this is to ensure that the selected designs are not null
- const defaultGeneratorDesigns = {
- ...selectedDesignTypes,
- palette: [selectedDesignTypes.palette],
- } as IGeneratorDesigns
-
- return {
- defaultGeneratorDesigns,
- message: 'Version generator designs are present.',
- }
-}
-
-const getVersionSelectedDesigns = async ({
- artworkVersionId,
-}: {
- artworkVersionId: IArtworkVersionWithChildren['id']
-}): Promise => {
- return await findManyDesignsWithType({
- where: { artworkVersionId, selected: true },
- })
-}
-
-// default/global design settings for each layer
-// layer can override any of these values
-const buildDefaultGeneratorLayer = async ({
- version,
- defaultGeneratorDesigns,
-}: {
- version: IArtworkVersionWithChildren
- defaultGeneratorDesigns: IGeneratorDesigns
-}): Promise => {
- const artworkVersionId = version.id
-
- // get all visible palettes to use for fill or stroke
- const palettes = await getArtworkVersionVisiblePalettes({
- artworkVersionId,
- })
-
- // get all visible rotates to use for rotate if visible random
- const rotates = await getRotates({
- artworkVersionId,
- rotate: defaultGeneratorDesigns.rotate,
- })
-
- // container defaults to version dimensions
- const container = getArtworkVersionContainer({ version })
-
- const assets = groupAssetsByType({ assets: version.assets })
-
- return {
- ...defaultGeneratorDesigns,
- palette: palettes,
- rotates,
- container,
- assets,
- }
-}
-
-const getArtworkVersionContainer = ({
- version,
-}: {
- version: IArtworkVersionWithChildren
-}) => {
- const { width, height } = version
- return {
- width,
- height,
- top: 0,
- left: 0,
- margin: 0,
- canvas: {
- width,
- height,
- },
- }
-}
-
-const buildGeneratorLayers = async ({
- layers,
- defaultGeneratorLayer,
-}: {
- layers: ILayerWithChildren[]
- defaultGeneratorLayer: ILayerGenerator
-}) => {
- const visibleLayers = filterLayersVisible({ layers }) as ILayerWithChildren[]
-
- return await Promise.all(
- visibleLayers.map(layer =>
- buildGeneratorLayer({
- layer,
- defaultGeneratorLayer,
- }),
- ),
- )
-}
-
-const buildGeneratorLayer = async ({
- layer,
- defaultGeneratorLayer,
-}: {
- layer: ILayerWithChildren
- defaultGeneratorLayer: ILayerGenerator
-}): Promise => {
- const layerId = layer.id
-
- // Step 1: get all selected designs for the layer
- const layerSelectedDesigns = await getLayerSelectedDesigns({ layerId })
-
- // Step 2: split the selected designs into the first of each type
- const selectedDesignTypes = findFirstDesignsByTypeInArray({
- designs: layerSelectedDesigns,
- })
-
- // Step 3: filter the selected designs that are present
- // separate the palette from the rest of the layer generator designs
- // if the layer has no palette we do not want to override the default palette
- const { palette, ...layerGeneratorDesigns } = filterSelectedDesignTypes({
- selectedDesignTypes,
- })
-
- // Step 4: initialize the generator layer
- // with the default generator layer
- // and the layer generator designs as overrides
- // and layer details
- const { id, name, description } = layer
-
- const assets = groupAssetsByType({ assets: layer.assets })
-
- const layerGenerator = {
- ...defaultGeneratorLayer,
- ...layerGeneratorDesigns,
- id,
- name,
- description,
- assets,
- }
-
- // Step 5: get all visible palettes to use for fill or stroke
- // if empty, then use the default palette
- const palettes = await getLayerVisiblePalettes({ layerId })
- if (palettes.length > 0) {
- layerGenerator.palette = palettes
- }
-
- // Step 6: get all visible rotates to use for rotate if visible random
- // if empty, then use the default rotate
- const { rotate } = layerGeneratorDesigns
- if (rotate) {
- const rotates = await getRotates({
- layerId,
- rotate,
- })
-
- if (rotates.length > 0) {
- layerGenerator.rotates = rotates
- }
- }
-
- return layerGenerator
-}
-
-const getLayerSelectedDesigns = async ({
- layerId,
-}: {
- layerId: ILayerWithChildren['id']
-}) => {
- return await findManyDesignsWithType({
- where: { layerId, selected: true },
- })
-}
-
-const getRotates = async ({
- artworkVersionId,
- layerId,
- rotate,
-}: {
- artworkVersionId?: IArtworkVersionWithChildren['id']
- layerId?: ILayerWithChildren['id']
- rotate: IRotate
-}) => {
- const allRotates = isArrayRotateBasisType(rotate.basis as rotateBasisTypeEnum)
-
- if (allRotates) {
- if (artworkVersionId) {
- return await getArtworkVersionVisibleRotates({ artworkVersionId })
- } else if (layerId) {
- return await getLayerVisibleRotates({ layerId })
- }
- }
- return []
-}
-
-const buildGeneratorWatermark = async ({
- version,
-}: {
- version: IArtworkVersionWithChildren
-}): Promise => {
- if (!version.watermark) return null
-
- const userInstagramUrl = await prisma.artworkBranch
- .findUnique({
- where: { id: version.branchId },
- select: {
- owner: {
- select: { sm_url_instagram: true },
- },
- },
- })
- .then(branch => branch?.owner?.sm_url_instagram)
-
- const text = userInstagramUrl
- ? `@${userInstagramUrl.split('/').pop()}`
- : 'PPPAAATTT'
-
- return {
- text,
- color: version.watermarkColor,
- }
-}
-
-const buildGeneratorMetadata = async ({
- version,
-}: {
- version: IArtworkVersionWithChildren
-}): Promise => {
- const branch = await prisma.artworkBranch.findUnique({
- where: { id: version.branchId },
- select: {
- id: true,
- name: true,
- slug: true,
- description: true,
- artwork: {
- select: {
- id: true,
- name: true,
- slug: true,
- description: true,
- project: {
- select: {
- id: true,
- name: true,
- slug: true,
- description: true,
- },
- },
- owner: {
- select: {
- id: true,
- name: true,
- username: true,
- },
- },
- },
- },
- },
- })
- invariant(branch, `Branch not found for version ${version.id}`)
- const artwork = branch.artwork
- invariant(artwork, `Artwork not found for branch ${branch.id}`)
- const project = artwork.project
- invariant(project, `Project not found for artwork ${artwork.id}`)
- const owner = artwork.owner
- invariant(owner, `Owner not found for artwork ${artwork.id}`)
-
- return {
- // version
- versionId: version.id,
- versionName: version.name,
- versionSlug: version.slug,
- versionDescription: version.description,
- // branch
- branchId: version.branchId,
- branchName: branch.name,
- branchSlug: branch.slug,
- branchDescription: branch.description,
- // artwork
- artworkId: artwork.id,
- artworkName: artwork.name,
- artworkSlug: artwork.slug,
- artworkDescription: artwork.description,
- // project
- projectId: project.id,
- projectName: project.name,
- projectSlug: project.slug,
- projectDescription: project.description,
- // owner
- ownerId: owner.id,
- ownerName: owner.name,
- ownerUsername: owner.username,
- }
-}
diff --git a/app/services/artwork/version/generator/build.verify.service.ts b/app/services/artwork/version/generator/build.verify.service.ts
new file mode 100644
index 00000000..986528e9
--- /dev/null
+++ b/app/services/artwork/version/generator/build.verify.service.ts
@@ -0,0 +1,66 @@
+import { type IGeneratorDesigns } from '#app/definitions/artwork-generator'
+import { type IArtworkVersionWithChildren } from '#app/models/artwork-version/artwork-version.server'
+import {
+ type IDesignWithType,
+ findManyDesignsWithType,
+} from '#app/models/design/design.server'
+import {
+ findFirstDesignsByTypeInArray,
+ verifySelectedDesignTypesAllPresent,
+} from '#app/utils/design'
+
+export const verifyDefaultGeneratorDesigns = async ({
+ version,
+}: {
+ version: IArtworkVersionWithChildren
+}): Promise<{
+ defaultGeneratorDesigns: IGeneratorDesigns | null
+ message: string
+}> => {
+ // Step 1: get all selected designs for the version
+ // design table has `selected: boolean` field
+ const selectedDesigns = await getVersionSelectedDesigns({
+ artworkVersionId: version.id,
+ })
+
+ // Step 2: split the selected designs into the first of each type
+ const selectedDesignTypes = findFirstDesignsByTypeInArray({
+ designs: selectedDesigns,
+ })
+
+ // Step 3: validate that all selected design types are present
+ // message will indicate which design type is missing
+ const { success, message } = verifySelectedDesignTypesAllPresent({
+ selectedDesignTypes,
+ })
+
+ // Step 4: return failure with message if selected designs are not valid
+ if (!success) {
+ return {
+ message,
+ defaultGeneratorDesigns: null,
+ }
+ }
+
+ // Step 5: reformat the selected designs to be generator designs
+ // this is to ensure that the selected designs are not null
+ const defaultGeneratorDesigns = {
+ ...selectedDesignTypes,
+ palette: [selectedDesignTypes.palette],
+ } as IGeneratorDesigns
+
+ return {
+ defaultGeneratorDesigns,
+ message: 'Version generator designs are present.',
+ }
+}
+
+const getVersionSelectedDesigns = async ({
+ artworkVersionId,
+}: {
+ artworkVersionId: IArtworkVersionWithChildren['id']
+}): Promise => {
+ return await findManyDesignsWithType({
+ where: { artworkVersionId, selected: true },
+ })
+}
diff --git a/app/services/artwork/version/generator/build.watermark.service.ts b/app/services/artwork/version/generator/build.watermark.service.ts
new file mode 100644
index 00000000..3cbd4045
--- /dev/null
+++ b/app/services/artwork/version/generator/build.watermark.service.ts
@@ -0,0 +1,31 @@
+import { type IGeneratorWatermark } from '#app/definitions/artwork-generator'
+import { type IArtworkVersionWithChildren } from '#app/models/artwork-version/artwork-version.server'
+import { prisma } from '#app/utils/db.server'
+
+export const buildGeneratorWatermark = async ({
+ version,
+}: {
+ version: IArtworkVersionWithChildren
+}): Promise => {
+ if (!version.watermark) return null
+
+ const userInstagramUrl = await prisma.artworkBranch
+ .findUnique({
+ where: { id: version.branchId },
+ select: {
+ owner: {
+ select: { sm_url_instagram: true },
+ },
+ },
+ })
+ .then(branch => branch?.owner?.sm_url_instagram)
+
+ const text = userInstagramUrl
+ ? `@${userInstagramUrl.split('/').pop()}`
+ : 'PPPAAATTT'
+
+ return {
+ text,
+ color: version.watermarkColor,
+ }
+}
diff --git a/app/services/asset.image.update.hide-on-draw.service.ts b/app/services/asset.image.update.hide-on-draw.service.ts
new file mode 100644
index 00000000..e35e327e
--- /dev/null
+++ b/app/services/asset.image.update.hide-on-draw.service.ts
@@ -0,0 +1,52 @@
+import { invariant } from '@epic-web/invariant'
+import { getAssetImage } from '#app/models/asset/image/image.get.server'
+import {
+ type IAssetImageUpdateHideOnDrawSubmission,
+ updateAssetImageHideOnDraw,
+} from '#app/models/asset/image/image.update.hide-on-draw.server'
+import { type IAssetImageUpdatedResponse } from '#app/models/asset/image/image.update.server'
+import { AssetAttributesImageSchema } from '#app/schema/asset/image'
+import { prisma } from '#app/utils/db.server'
+
+export const assetImageUpdateHideOnDrawService = async ({
+ userId,
+ id,
+}: IAssetImageUpdateHideOnDrawSubmission): Promise => {
+ try {
+ // Step 1: verify the asset image exists
+ const assetImage = await getAssetImage({
+ where: { id, ownerId: userId },
+ })
+ invariant(assetImage, 'Asset Image not found')
+ const { attributes: assetImageAttributes } = assetImage
+
+ // Step 2: validate asset image data
+ const data = {
+ ...assetImageAttributes,
+ hideOnDraw: !assetImageAttributes.hideOnDraw,
+ }
+ const assetImageData = AssetAttributesImageSchema.parse(data)
+
+ // Step 3: update the asset image via promise
+ const updateAssetImagePromise = updateAssetImageHideOnDraw({
+ id,
+ data: { attributes: { ...assetImageData } },
+ })
+
+ // Step 4: execute the transaction
+ const [updatedAssetImage] = await prisma.$transaction([
+ updateAssetImagePromise,
+ ])
+
+ return {
+ updatedAssetImage,
+ success: true,
+ }
+ } catch (error) {
+ console.error(error)
+ return {
+ success: false,
+ message: 'Unknown error: assetImageUpdateHideOnDrawService',
+ }
+ }
+}
diff --git a/app/services/canvas/draw-background.service.ts b/app/services/canvas/draw-background.service.ts
index c71bd3f0..8055557f 100644
--- a/app/services/canvas/draw-background.service.ts
+++ b/app/services/canvas/draw-background.service.ts
@@ -1,5 +1,11 @@
import { type IArtworkVersionGenerator } from '#app/definitions/artwork-generator'
+const canvasDimensions = ({ canvas }: { canvas: HTMLCanvasElement }) => {
+ const { width, height } = canvas
+ return { width, height, ratio: width / height }
+}
+
+// do this at the beginning
export const canvasDrawBackgroundService = ({
ctx,
generator,
@@ -12,3 +18,20 @@ export const canvasDrawBackgroundService = ({
ctx.fillStyle = `#${background}`
ctx.fillRect(0, 0, width, height)
}
+
+// do this if drawing an image to get pixel data
+// then redraw the background to clear the canvas
+export const canvasRedrawDrawBackgroundService = ({
+ ctx,
+ background,
+}: {
+ ctx: CanvasRenderingContext2D
+ background: string
+}) => {
+ const { width: canvasWidth, height: canvasHeight } = canvasDimensions({
+ canvas: ctx.canvas,
+ })
+
+ ctx.fillStyle = `#${background}`
+ ctx.fillRect(0, 0, canvasWidth, canvasHeight)
+}
diff --git a/app/services/canvas/draw.load-assets.service.ts b/app/services/canvas/draw.load-assets.service.ts
new file mode 100644
index 00000000..6a5573ed
--- /dev/null
+++ b/app/services/canvas/draw.load-assets.service.ts
@@ -0,0 +1,48 @@
+import { type IArtworkVersionGenerator } from '#app/definitions/artwork-generator'
+import { getAssetImgSrc } from '#app/models/asset/image/utils'
+import { loadImage } from '#app/utils/image'
+
+// loaded assets:
+// key: asset id
+// value: image element, or something else when other assets are added
+
+export interface ILoadedAssets {
+ [key: string]: HTMLImageElement
+}
+
+export const canvasDrawLoadAssetsService = async ({
+ generator,
+}: {
+ generator: IArtworkVersionGenerator
+}): Promise => {
+ const { layers } = generator
+ const loadedAssets: ILoadedAssets = {}
+
+ const imageLoadPromises: Promise[] = []
+
+ for (const layer of layers) {
+ const {
+ assets: { assetImages },
+ } = layer
+
+ for (const image of assetImages) {
+ const src = getAssetImgSrc({ image })
+ const loadPromise = loadImage({ src })
+ .then(img => {
+ loadedAssets[image.id] = img
+ })
+ .catch(error => {
+ console.error(
+ `Failed to load image with ID ${image.id} from source: ${src}`,
+ error,
+ )
+ })
+
+ imageLoadPromises.push(loadPromise)
+ }
+ }
+
+ await Promise.all(imageLoadPromises)
+
+ return loadedAssets
+}
diff --git a/app/services/canvas/draw.service.ts b/app/services/canvas/draw.service.ts
index 82cde916..1bc16673 100644
--- a/app/services/canvas/draw.service.ts
+++ b/app/services/canvas/draw.service.ts
@@ -2,16 +2,20 @@ import { invariant } from '@epic-web/invariant'
import { type IArtworkVersionGenerator } from '#app/definitions/artwork-generator'
import { canvasDrawBackgroundService } from './draw-background.service'
import { canvasDrawWatermarkService } from './draw-watermark.service'
+import { canvasDrawLoadAssetsService } from './draw.load-assets.service'
import { canvasLayerBuildDrawLayersService } from './layer/build/build-draw-layers.service'
import { canvasDrawLayersService } from './layer/draw/draw-layers.service'
-export const canvasDrawService = ({
+export const canvasDrawService = async ({
canvas,
generator,
}: {
canvas: HTMLCanvasElement
generator: IArtworkVersionGenerator
}) => {
+ // Step 0: load assets
+ const loadedAssets = await canvasDrawLoadAssetsService({ generator })
+
// Step 1: get canvas
const ctx = getContext(canvas)
@@ -22,11 +26,11 @@ export const canvasDrawService = ({
const drawLayers = canvasLayerBuildDrawLayersService({
ctx,
generator,
+ loadedAssets,
})
- console.log('drawLayers count: ', drawLayers.length)
// Step 4: draw layers to canvas
- canvasDrawLayersService({ ctx, drawLayers })
+ canvasDrawLayersService({ ctx, drawLayers, loadedAssets })
// Step 5: draw watermark
canvasDrawWatermarkService({ ctx, generator })
diff --git a/app/services/canvas/layer/build/build-draw-layers.layer.assets.service.ts b/app/services/canvas/layer/build/build-draw-layers.layer.assets.service.ts
new file mode 100644
index 00000000..dc8e8f71
--- /dev/null
+++ b/app/services/canvas/layer/build/build-draw-layers.layer.assets.service.ts
@@ -0,0 +1,17 @@
+import { type ILayerGenerator } from '#app/definitions/artwork-generator'
+import { type IAssetGenerationByType } from '#app/models/asset/asset.generation.server'
+import { canvasBuildLayerDrawImageService } from './build-layer-draw-image.service'
+
+export const buildLayerGenerationAssets = ({
+ ctx,
+ layer,
+}: {
+ ctx: CanvasRenderingContext2D
+ layer: ILayerGenerator
+}): IAssetGenerationByType => {
+ const assetImages = canvasBuildLayerDrawImageService({ ctx, layer })
+
+ return {
+ assetImages,
+ }
+}
diff --git a/app/services/canvas/layer/build/build-draw-layers.layer.items.service.ts b/app/services/canvas/layer/build/build-draw-layers.layer.items.service.ts
new file mode 100644
index 00000000..5d5ec683
--- /dev/null
+++ b/app/services/canvas/layer/build/build-draw-layers.layer.items.service.ts
@@ -0,0 +1,86 @@
+import {
+ type IGenerationItem,
+ type ILayerGenerator,
+} from '#app/definitions/artwork-generator'
+import { type IAssetGenerationByType } from '#app/models/asset/asset.generation.server'
+import { canvasRedrawDrawBackgroundService } from '../../draw-background.service'
+import { type ILoadedAssets } from '../../draw.load-assets.service'
+import { canvasDrawLayerAssets } from '../draw/draw-layers.asset.service'
+import { canvasBuildLayerDrawCountService } from './build-layer-draw-count.service'
+import { canvasBuildLayerDrawFillService } from './build-layer-draw-fill.service'
+import { canvasBuildLayerDrawLineService } from './build-layer-draw-line.service'
+import { shouldGetPixelHex } from './build-layer-draw-position.pixel.service'
+import { canvasBuildLayerDrawPositionService } from './build-layer-draw-position.service'
+import { canvasBuildLayerDrawRotateService } from './build-layer-draw-rotate.service'
+import { canvasBuildLayerDrawSizeService } from './build-layer-draw-size.service'
+import { canvasBuildLayerDrawStrokeService } from './build-layer-draw-stroke.service'
+import { canvasBuildLayerDrawTemplateService } from './build-layer-draw-template.service'
+
+export const buildLayerGenerationItems = ({
+ ctx,
+ layer,
+ assets,
+ loadedAssets,
+}: {
+ ctx: CanvasRenderingContext2D
+ layer: ILayerGenerator
+ assets: IAssetGenerationByType
+ loadedAssets: ILoadedAssets
+}): IGenerationItem[] => {
+ const count = canvasBuildLayerDrawCountService({ layer })
+ const drawAssets = shouldGetPixelHex({ layer })
+
+ if (drawAssets) {
+ canvasDrawLayerAssets({ ctx, assets, loadedAssets })
+ }
+
+ const layerGenerationItems = []
+ for (let index = 0; index < count; index++) {
+ const generationItem = buildLayerGenerationItem({
+ ctx,
+ layer,
+ index,
+ count,
+ })
+ layerGenerationItems.push(generationItem)
+ }
+
+ canvasRedrawDrawBackgroundService({ ctx, background: layer.background })
+
+ return layerGenerationItems
+}
+
+const buildLayerGenerationItem = ({
+ ctx,
+ layer,
+ index,
+ count,
+}: {
+ ctx: CanvasRenderingContext2D
+ layer: ILayerGenerator
+ index: number
+ count: number
+}): IGenerationItem => {
+ const { x, y, pixelHex } = canvasBuildLayerDrawPositionService({
+ ctx,
+ layer,
+ index,
+ })
+ const size = canvasBuildLayerDrawSizeService({ layer, index })
+ const fill = canvasBuildLayerDrawFillService({ layer, index, pixelHex })
+ const stroke = canvasBuildLayerDrawStrokeService({ layer, index, pixelHex })
+ const line = canvasBuildLayerDrawLineService({ layer, index })
+ const rotate = canvasBuildLayerDrawRotateService({ layer, index })
+ const template = canvasBuildLayerDrawTemplateService({ layer, index })
+
+ return {
+ id: `layer-${layer.id}-${index}-${count}`,
+ fillStyle: fill,
+ lineWidth: line,
+ position: { x, y },
+ rotate,
+ size,
+ strokeStyle: stroke,
+ template,
+ }
+}
diff --git a/app/services/canvas/layer/build/build-draw-layers.layer.service.ts b/app/services/canvas/layer/build/build-draw-layers.layer.service.ts
new file mode 100644
index 00000000..622e5ca2
--- /dev/null
+++ b/app/services/canvas/layer/build/build-draw-layers.layer.service.ts
@@ -0,0 +1,25 @@
+import {
+ type IGenerationLayer,
+ type ILayerGenerator,
+} from '#app/definitions/artwork-generator'
+import { type ILoadedAssets } from '../../draw.load-assets.service'
+import { buildLayerGenerationAssets } from './build-draw-layers.layer.assets.service'
+import { buildLayerGenerationItems } from './build-draw-layers.layer.items.service'
+
+export const canvasLayerBuildDrawLayersLayerService = ({
+ ctx,
+ layer,
+ loadedAssets,
+}: {
+ ctx: CanvasRenderingContext2D
+ layer: ILayerGenerator
+ loadedAssets: ILoadedAssets
+}): IGenerationLayer => {
+ const assets = buildLayerGenerationAssets({ ctx, layer })
+ const items = buildLayerGenerationItems({ ctx, layer, assets, loadedAssets })
+ return {
+ generator: layer,
+ assets,
+ items,
+ }
+}
diff --git a/app/services/canvas/layer/build/build-draw-layers.service.ts b/app/services/canvas/layer/build/build-draw-layers.service.ts
index 32a9a69d..586723cc 100644
--- a/app/services/canvas/layer/build/build-draw-layers.service.ts
+++ b/app/services/canvas/layer/build/build-draw-layers.service.ts
@@ -1,107 +1,26 @@
import {
type IGenerationLayer,
type IArtworkVersionGenerator,
- type IGenerationItem,
- type ILayerGenerator,
- type IGenerationAssets,
} from '#app/definitions/artwork-generator'
-import { canvasBuildLayerDrawCountService } from './build-layer-draw-count.service'
-import { canvasBuildLayerDrawFillService } from './build-layer-draw-fill.service'
-import { canvasBuildLayerDrawImageService } from './build-layer-draw-image.service'
-import { canvasBuildLayerDrawLineService } from './build-layer-draw-line.service'
-import { canvasBuildLayerDrawPositionService } from './build-layer-draw-position.service'
-import { canvasBuildLayerDrawRotateService } from './build-layer-draw-rotate.service'
-import { canvasBuildLayerDrawSizeService } from './build-layer-draw-size.service'
-import { canvasBuildLayerDrawStrokeService } from './build-layer-draw-stroke.service'
-import { canvasBuildLayerDrawTemplateService } from './build-layer-draw-template.service'
+import { type ILoadedAssets } from '../../draw.load-assets.service'
+import { canvasLayerBuildDrawLayersLayerService } from './build-draw-layers.layer.service'
export const canvasLayerBuildDrawLayersService = ({
ctx,
generator,
+ loadedAssets,
}: {
ctx: CanvasRenderingContext2D
generator: IArtworkVersionGenerator
+ loadedAssets: ILoadedAssets
}): IGenerationLayer[] => {
const { layers } = generator
- return layers.map(layer => {
- const assets = buildLayerGenerationAssets({ ctx, layer })
- const items = buildLayerGenerationItems({ ctx, layer })
- return {
- generator: layer,
- assets,
- items,
- }
- })
-}
-
-const buildLayerGenerationAssets = ({
- ctx,
- layer,
-}: {
- ctx: CanvasRenderingContext2D
- layer: ILayerGenerator
-}): IGenerationAssets => {
- const image = canvasBuildLayerDrawImageService({ ctx, layer })
-
- return {
- image,
- }
-}
-
-const buildLayerGenerationItems = ({
- ctx,
- layer,
-}: {
- ctx: CanvasRenderingContext2D
- layer: ILayerGenerator
-}): IGenerationItem[] => {
- const count = canvasBuildLayerDrawCountService({ layer })
-
- const layerGenerationItems = []
- for (let index = 0; index < count; index++) {
- const generationItem = buildLayerGenerationItem({
+ return layers.map(layer =>
+ canvasLayerBuildDrawLayersLayerService({
ctx,
layer,
- index,
- count,
- })
- layerGenerationItems.push(generationItem)
- }
- return layerGenerationItems
-}
-
-const buildLayerGenerationItem = ({
- ctx,
- layer,
- index,
- count,
-}: {
- ctx: CanvasRenderingContext2D
- layer: ILayerGenerator
- index: number
- count: number
-}): IGenerationItem => {
- const { x, y, pixelHex } = canvasBuildLayerDrawPositionService({
- ctx,
- layer,
- index,
- })
- const size = canvasBuildLayerDrawSizeService({ layer, index })
- const fill = canvasBuildLayerDrawFillService({ layer, index, pixelHex })
- const stroke = canvasBuildLayerDrawStrokeService({ layer, index, pixelHex })
- const line = canvasBuildLayerDrawLineService({ layer, index })
- const rotate = canvasBuildLayerDrawRotateService({ layer, index })
- const template = canvasBuildLayerDrawTemplateService({ layer, index })
-
- return {
- id: `layer-${layer.id}-${index}-${count}`,
- fillStyle: fill,
- lineWidth: line,
- position: { x, y },
- rotate,
- size,
- strokeStyle: stroke,
- template,
- }
+ loadedAssets,
+ }),
+ )
}
diff --git a/app/services/canvas/layer/build/build-layer-draw-image.fit.service.ts b/app/services/canvas/layer/build/build-layer-draw-image.fit.service.ts
index d54ae54b..d56a4172 100644
--- a/app/services/canvas/layer/build/build-layer-draw-image.fit.service.ts
+++ b/app/services/canvas/layer/build/build-layer-draw-image.fit.service.ts
@@ -68,7 +68,7 @@ const imageFitCover = ({
}: {
ctx: CanvasRenderingContext2D
image: IAssetImage
-}): IAssetImageDrawGeneration => {
+}) => {
const {
// width: imageWidth,
// height: imageHeight,
@@ -168,7 +168,7 @@ export const canvasBuildLayerDrawImageFitService = ({
}: {
ctx: CanvasRenderingContext2D
image: IAssetImage
-}) => {
+}): IAssetImageDrawGeneration => {
switch (image.attributes.fit) {
case AssetImageFitTypeEnum.FILL:
return imageFitFill({ ctx })
@@ -181,6 +181,6 @@ export const canvasBuildLayerDrawImageFitService = ({
case AssetImageFitTypeEnum.SCALE_DOWN:
return imageFitScaleDown({ ctx, image })
default:
- return null
+ return imageFitNone({ ctx, image })
}
}
diff --git a/app/services/canvas/layer/build/build-layer-draw-image.service.ts b/app/services/canvas/layer/build/build-layer-draw-image.service.ts
index 13f74f52..f2436e21 100644
--- a/app/services/canvas/layer/build/build-layer-draw-image.service.ts
+++ b/app/services/canvas/layer/build/build-layer-draw-image.service.ts
@@ -12,23 +12,25 @@ export const canvasBuildLayerDrawImageService = ({
}: {
ctx: CanvasRenderingContext2D
layer: ILayerGenerator
-}): IAssetImageGeneration | null => {
+}): IAssetImageGeneration[] => {
const { assets } = layer
const { assetImages } = assets
- if (!assetImages.length) return null
+ if (!assetImages.length) return []
- // just one image to start
- const image = assetImages[0]
+ return assetImages.map(image => {
+ const src = getAssetImgSrc({ image })
+ const fit = canvasBuildLayerDrawImageFitService({
+ ctx,
+ image,
+ }) as IAssetImageDrawGeneration
+ const hideOnDraw = image.attributes.hideOnDraw || false
- const src = getAssetImgSrc({ image })
- const fit = canvasBuildLayerDrawImageFitService({
- ctx,
- image,
- }) as IAssetImageDrawGeneration
-
- return {
- src,
- ...fit,
- }
+ return {
+ id: image.id,
+ src,
+ ...fit,
+ hideOnDraw,
+ }
+ })
}
diff --git a/app/services/canvas/layer/build/build-layer-draw-position.grid.service.ts b/app/services/canvas/layer/build/build-layer-draw-position.grid.service.ts
new file mode 100644
index 00000000..b94f3f4b
--- /dev/null
+++ b/app/services/canvas/layer/build/build-layer-draw-position.grid.service.ts
@@ -0,0 +1,36 @@
+import { type ILayerGenerator } from '#app/definitions/artwork-generator'
+
+export const getGridPosition = ({
+ layer,
+ index,
+ ctx,
+}: {
+ layer: ILayerGenerator
+ index: number
+ ctx: CanvasRenderingContext2D
+}) => {
+ const { layout, container } = layer
+ const { rows, columns } = layout
+ const { width, height, top, left } = container
+
+ // subtract 1 from denominator to get the proper step size
+ // for example, if columns is 10, we want 10 divide by 9
+ // so that the first and last positions on the grid are the edges
+ // and not cells in the middle
+ const stepX = width / (columns - 1)
+ const stepY = height / (rows - 1)
+
+ // quotient is the row index
+ const rowIndex = Math.floor(index / columns)
+ // remainder is the column index
+ const columnIndex = index % columns
+
+ // calculate the x and y position based on the row and column index
+ // and the step size
+ // add the top and left values to get the actual position
+ // add margin later
+ const x = columnIndex * stepX + left
+ const y = rowIndex * stepY + top
+
+ return { x, y }
+}
diff --git a/app/services/canvas/layer/build/build-layer-draw-position.pixel.service.ts b/app/services/canvas/layer/build/build-layer-draw-position.pixel.service.ts
new file mode 100644
index 00000000..c3218d7f
--- /dev/null
+++ b/app/services/canvas/layer/build/build-layer-draw-position.pixel.service.ts
@@ -0,0 +1,96 @@
+import { type ILayerGenerator } from '#app/definitions/artwork-generator'
+import { FillBasisTypeEnum } from '#app/schema/fill'
+import { StrokeBasisTypeEnum } from '#app/schema/stroke'
+
+export const shouldGetPixelHex = ({ layer }: { layer: ILayerGenerator }) => {
+ const { fill, stroke } = layer
+ return (
+ fill.basis === FillBasisTypeEnum.PIXEL ||
+ stroke.basis === StrokeBasisTypeEnum.PIXEL
+ )
+}
+
+// Function to get the hex color value at a specific pixel
+export const getHexAtPixel = ({
+ ctx,
+ x,
+ y,
+}: {
+ ctx: CanvasRenderingContext2D
+ x: number
+ y: number
+}) => {
+ // Adjust the position for the nearest pixel (see below)
+ const { xAdj, yAdj } = adjustPositionForNearestPixel({ x, y, ctx })
+ // Get the image data at the pixel
+ const pixelData = buildPixelImageData({ ctx, x: xAdj, y: yAdj })
+ // Extract RGB values from the pixel data
+ const { r, g, b } = buildPixelRGB({ data: pixelData.data })
+ // Convert RGB to hex
+ const hex = componentToHex(r) + componentToHex(g) + componentToHex(b)
+ // Return the hex value in uppercase
+ return hex.toUpperCase()
+}
+
+// if the x or y value are outside the canvas or on right/bottom edge
+// there will be no pixel data, so we need to adjust the position
+// this is a semi-temporary fix
+// sometimes random positions are on the edge
+// right/bottom of grid too
+const adjustPositionForNearestPixel = ({
+ x,
+ y,
+ ctx,
+}: {
+ x: number
+ y: number
+ ctx: CanvasRenderingContext2D
+}) => {
+ const { canvas } = ctx
+ const { width, height } = canvas
+ if (x < 0) {
+ x = 0
+ }
+ if (x >= width) {
+ x = width - 1
+ }
+ if (y < 0) {
+ y = 0
+ }
+ if (y >= height) {
+ y = height - 1
+ }
+ return { xAdj: x, yAdj: y }
+}
+
+// Function to build the image data for a pixel
+const buildPixelImageData = ({
+ ctx,
+ x,
+ y,
+}: {
+ ctx: CanvasRenderingContext2D
+ x: number
+ y: number
+}): ImageData => {
+ // image data for a 1x1 pixel area, effectively getting data for a single pixel
+ return ctx.getImageData(x, y, 1, 1)
+}
+
+// Function to build the RGB values from image data
+const buildPixelRGB = ({ data }: { data: Uint8ClampedArray }) => {
+ const r = data[0] // Red value
+ const g = data[1] // Green value
+ const b = data[2] // Blue value
+ // The alpha value at index 3 is not included as we are only interested in the RGB values for color representation.
+ return { r, g, b }
+}
+
+// Function to convert a component of RGB to hex
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString#description
+function componentToHex(c: number) {
+ // Convert the number to hex
+ const hex = c.toString(16)
+ // Ensure 2 digits by adding a leading zero if necessary
+ return hex.length == 1 ? '0' + hex : hex
+}
diff --git a/app/services/canvas/layer/build/build-layer-draw-position.service.ts b/app/services/canvas/layer/build/build-layer-draw-position.service.ts
index 0e3d9bc4..1cd4766c 100644
--- a/app/services/canvas/layer/build/build-layer-draw-position.service.ts
+++ b/app/services/canvas/layer/build/build-layer-draw-position.service.ts
@@ -1,8 +1,11 @@
import { type ILayerGenerator } from '#app/definitions/artwork-generator'
-import { FillBasisTypeEnum } from '#app/schema/fill'
import { LayoutStyleTypeEnum } from '#app/schema/layout'
-import { StrokeBasisTypeEnum } from '#app/schema/stroke'
import { randomInRange } from '#app/utils/random.utils'
+import { getGridPosition } from './build-layer-draw-position.grid.service'
+import {
+ getHexAtPixel,
+ shouldGetPixelHex,
+} from './build-layer-draw-position.pixel.service'
export const canvasBuildLayerDrawPositionService = ({
ctx,
@@ -52,131 +55,3 @@ const getRandomPosition = ({ layer }: { layer: ILayerGenerator }) => {
const y = randomInRange(top, height)
return { x, y }
}
-
-const getGridPosition = ({
- layer,
- index,
- ctx,
-}: {
- layer: ILayerGenerator
- index: number
- ctx: CanvasRenderingContext2D
-}) => {
- const { layout, container } = layer
- const { rows, columns } = layout
- const { width, height, top, left } = container
-
- // subtract 1 from denominator to get the proper step size
- // for example, if columns is 10, we want 10 divide by 9
- // so that the first and last positions on the grid are the edges
- // and not cells in the middle
- const stepX = width / (columns - 1)
- const stepY = height / (rows - 1)
-
- // quotient is the row index
- const rowIndex = Math.floor(index / columns)
- // remainder is the column index
- const columnIndex = index % columns
-
- // calculate the x and y position based on the row and column index
- // and the step size
- // add the top and left values to get the actual position
- // add margin later
- const x = columnIndex * stepX + left
- const y = rowIndex * stepY + top
-
- return { x, y }
-}
-
-const shouldGetPixelHex = ({ layer }: { layer: ILayerGenerator }) => {
- const { fill, stroke } = layer
- return (
- fill.basis === FillBasisTypeEnum.PIXEL ||
- stroke.basis === StrokeBasisTypeEnum.PIXEL
- )
-}
-
-// Function to get the hex color value at a specific pixel
-const getHexAtPixel = ({
- ctx,
- x,
- y,
-}: {
- ctx: CanvasRenderingContext2D
- x: number
- y: number
-}) => {
- // Adjust the position for the nearest pixel (see below)
- const { xAdj, yAdj } = adjustPositionForNearestPixel({ x, y, ctx })
- // Get the image data at the pixel
- const pixelData = buildPixelImageData({ ctx, x: xAdj, y: yAdj })
- // Extract RGB values from the pixel data
- const { r, g, b } = buildPixelRGB({ data: pixelData.data })
- // Convert RGB to hex
- const hex = componentToHex(r) + componentToHex(g) + componentToHex(b)
- // Return the hex value in uppercase
- return hex.toUpperCase()
-}
-
-// if the x or y value are outside the canvas or on right/bottom edge
-// there will be no pixel data, so we need to adjust the position
-// this is a semi-temporary fix
-// sometimes random positions are on the edge
-// right/bottom of grid too
-const adjustPositionForNearestPixel = ({
- x,
- y,
- ctx,
-}: {
- x: number
- y: number
- ctx: CanvasRenderingContext2D
-}) => {
- const { canvas } = ctx
- const { width, height } = canvas
- if (x < 0) {
- x = 0
- }
- if (x >= width) {
- x = width - 1
- }
- if (y < 0) {
- y = 0
- }
- if (y >= height) {
- y = height - 1
- }
- return { xAdj: x, yAdj: y }
-}
-
-// Function to build the image data for a pixel
-const buildPixelImageData = ({
- ctx,
- x,
- y,
-}: {
- ctx: CanvasRenderingContext2D
- x: number
- y: number
-}): ImageData => {
- // image data for a 1x1 pixel area, effectively getting data for a single pixel
- return ctx.getImageData(x, y, 1, 1)
-}
-
-// Function to build the RGB values from image data
-const buildPixelRGB = ({ data }: { data: Uint8ClampedArray }) => {
- const r = data[0] // Red value
- const g = data[1] // Green value
- const b = data[2] // Blue value
- // The alpha value at index 3 is not included as we are only interested in the RGB values for color representation.
- return { r, g, b }
-}
-
-// Function to convert a component of RGB to hex
-// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString#description
-function componentToHex(c: number) {
- // Convert the number to hex
- const hex = c.toString(16)
- // Ensure 2 digits by adding a leading zero if necessary
- return hex.length == 1 ? '0' + hex : hex
-}
diff --git a/app/services/canvas/layer/draw/draw-layers.asset.service.ts b/app/services/canvas/layer/draw/draw-layers.asset.service.ts
new file mode 100644
index 00000000..3982a327
--- /dev/null
+++ b/app/services/canvas/layer/draw/draw-layers.asset.service.ts
@@ -0,0 +1,27 @@
+import { type IAssetGenerationByType } from '#app/models/asset/asset.generation.server'
+import { type ILoadedAssets } from '../../draw.load-assets.service'
+
+export const canvasDrawLayerAssets = ({
+ ctx,
+ assets,
+ loadedAssets,
+ timeToDraw,
+}: {
+ ctx: CanvasRenderingContext2D
+ assets: IAssetGenerationByType
+ loadedAssets: ILoadedAssets
+ timeToDraw?: boolean
+}) => {
+ const { assetImages } = assets
+
+ for (let i = 0; i < assetImages.length; i++) {
+ const image = assetImages[i]
+
+ // may want to draw image to get pixel data on build
+ if (timeToDraw && image.hideOnDraw) continue
+
+ const img = loadedAssets[image.id]
+ const { x, y, width, height } = image
+ ctx.drawImage(img, x, y, width, height)
+ }
+}
diff --git a/app/services/canvas/layer/draw/draw-layers.service.ts b/app/services/canvas/layer/draw/draw-layers.service.ts
index cda10fc3..f1dbec28 100644
--- a/app/services/canvas/layer/draw/draw-layers.service.ts
+++ b/app/services/canvas/layer/draw/draw-layers.service.ts
@@ -1,44 +1,32 @@
import {
type IGenerationLayer,
type IGenerationItem,
- type IGenerationAssets,
} from '#app/definitions/artwork-generator'
-import { loadImage } from '#app/utils/image'
+import { type ILoadedAssets } from '../../draw.load-assets.service'
import { drawLayerItemService } from './draw-layer-item.service'
+import { canvasDrawLayerAssets } from './draw-layers.asset.service'
export const canvasDrawLayersService = ({
ctx,
drawLayers,
+ loadedAssets,
}: {
ctx: CanvasRenderingContext2D
drawLayers: IGenerationLayer[]
+ loadedAssets: ILoadedAssets
}) => {
for (let i = 0; i < drawLayers.length; i++) {
const layer = drawLayers[i]
- drawLayerAssets({ ctx, assets: layer.assets })
+ canvasDrawLayerAssets({
+ ctx,
+ assets: layer.assets,
+ loadedAssets,
+ timeToDraw: true,
+ })
drawLayerItems({ ctx, items: layer.items })
}
}
-const drawLayerAssets = ({
- ctx,
- assets,
-}: {
- ctx: CanvasRenderingContext2D
- assets: IGenerationAssets
-}) => {
- const { image } = assets
- if (image && image.src) {
- console.log('assets.image: ', image)
- const img = loadImage({ src: image.src })
- console.log('img: ', img)
- // load image
- // async
- // const { x, y, width, height } = assets.image
- // ctx.drawImage(img, x, y, width, height)
- }
-}
-
const drawLayerItems = ({
ctx,
items,
@@ -48,7 +36,6 @@ const drawLayerItems = ({
}) => {
for (let i = 0; i < items.length; i++) {
const layerDrawItem = items[i]
- // console.log('count: ', i)
drawLayerItemService({ ctx, layerDrawItem })
}
}
diff --git a/app/utils/routes.const.ts b/app/utils/routes.const.ts
index b5a778c0..b5ec2112 100644
--- a/app/utils/routes.const.ts
+++ b/app/utils/routes.const.ts
@@ -58,6 +58,7 @@ export const Routes = {
},
UPDATE: {
FIT: `${pathBase}/asset/image/update/fit`,
+ HIDE_ON_DRAW: `${pathBase}/asset/image/update/hide-on-draw`,
},
},
},