Skip to content

Commit

Permalink
feat: added new ingredients: flip, mirror, grayscale and `solar…
Browse files Browse the repository at this point in the history
…ize`
  • Loading branch information
darmiel committed Nov 4, 2023
1 parent 31cce72 commit d6d9164
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 91 deletions.
13 changes: 7 additions & 6 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import PopoverAddIngredient from "@/components/modals/PopoverAddIngredient"
import RecipeCard from "@/components/recipe/RecipeCard"
import Warning from "@/components/ui/Warning"
import { useSearch } from "@/context/SearchContext"
import { Options, Recipe, ingredientMeta, recipes } from "@/util/recipe"
import { IngredientOptions, ingredients } from "@/util/ingredients"
import { Recipe, recipes } from "@/util/recipe"
import {
Button,
Card,
Expand Down Expand Up @@ -380,7 +381,7 @@ export default function Home() {
{selectedRecipe.ingredients.map((ingredient, index) => (
<IngredientCard
key={index}
meta={ingredientMeta[ingredient.id]}
meta={ingredients[ingredient.id]}
name={ingredient.id}
enabled={!ingredient.disabled}
setEnabled={() => {
Expand Down Expand Up @@ -408,10 +409,10 @@ export default function Home() {

<PopoverAddIngredient
onAdd={(name, meta) => {
const options: Options = {}
meta.param_info &&
Object.entries(meta.param_info).forEach(([name, info]) => {
info.default && (options[name] = info.default)
const options: IngredientOptions = {}
meta.parameters &&
Object.entries(meta.parameters).forEach(([name, info]) => {
info.default !== undefined && (options[name] = info.default)
})
setSelectedRecipe((prev) => {
return {
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/ingredient/IngredientCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ValueEdit from "@/components/ingredient/ValueEdit"
import { IngredientMeta, Options } from "@/util/recipe"
import { IngredientInfo, IngredientOptions } from "@/util/ingredients"
import { Button, Checkbox, Tooltip } from "@nextui-org/react"
import clsx from "clsx"
import { useState } from "react"
Expand All @@ -15,11 +15,11 @@ export default function IngredientCard({
onRemove,
}: {
name: string
meta: IngredientMeta
options?: Options
meta: IngredientInfo
options?: IngredientOptions
enabled?: boolean
setEnabled?: (enabled: boolean) => void
onOptionsUpdate?: (newOptions: Options) => void
onOptionsUpdate?: (newOptions: IngredientOptions) => void
onRemove?: () => void
}) {
const [showOptions, setShowOptions] = useState(false)
Expand Down Expand Up @@ -92,7 +92,7 @@ export default function IngredientCard({
{options && showOptions && onOptionsUpdate && (
<div className="flex flex-wrap gap-2">
{Object.entries(options).map(([name, value]) => {
const helpText = meta.param_info[name].description
const helpText = meta.parameters[name].description
const paramStartContent = (
<code className="flex items-center space-x-1 rounded-l-sm bg-neutral-700 px-2 py-1">
<span>{name}</span>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/modals/PopoverAddIngredient.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import IngredientCard from "@/components/ingredient/IngredientCard"
import { IngredientMeta, ingredientMeta } from "@/util/recipe"
import { IngredientInfo, ingredients } from "@/util/ingredients"
import {
Button,
Input,
Expand All @@ -14,7 +14,7 @@ import { FaMagnifyingGlass, FaPlus } from "react-icons/fa6"
export default function PopoverAddIngredient({
onAdd,
}: {
onAdd: (ingredient_name: string, ingredient_meta: IngredientMeta) => void
onAdd: (ingredient_name: string, ingredient_meta: IngredientInfo) => void
}) {
const [search, setSearch] = useState("")

Expand All @@ -36,7 +36,7 @@ export default function PopoverAddIngredient({
/>

<ScrollShadow className="mt-4 flex max-h-80 flex-col space-y-2">
{Object.entries(ingredientMeta)
{Object.entries(ingredients)
.filter(
([name]) =>
!search || name.toLowerCase().includes(search.toLowerCase()),
Expand Down
134 changes: 134 additions & 0 deletions frontend/src/util/ingredients.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { ReactNode } from "react"
import {
FaArrowUpRightDots,
FaArrowsLeftRight,
FaArrowsUpDown,
FaCopy,
FaFill,
FaGlasses,
FaMoon,
FaStairs,
FaUncharted,
FaWaveSquare,
} from "react-icons/fa6"

/**
* Recipe parameters for a single ingredient.
*/
export type IngredientOptions = Record<string, unknown>

/**
* An ingredient is a single modification that can be applied to an image.
*/
export type Ingredient = {
id: string
with?: IngredientOptions
disabled?: boolean
}

/**
* The option info for a single ingredient.
* This is used to configure the ingredient in the UI.
*/
export type IngredientOptionInfo = {
description: string
default: unknown
}

/**
* The info for a single ingredient.
* This is used to display the ingredient in the UI.
*/
export type IngredientInfo = {
icon: ReactNode
description: string
parameters: Record<string, IngredientOptionInfo>
}

export const ingredients: Record<string, IngredientInfo> = {
exponential_noise: {
icon: <FaArrowUpRightDots />,
description: "Adds random variation",
parameters: {
scale: {
description: "Amount of noise to add",
default: 30,
},
},
},
sharpness: {
icon: <FaGlasses />,
description: "Enhances image edge definition",
parameters: {
factor: {
description: "Amount of sharpness to add",
default: 100,
},
},
},
contrast: {
icon: <FaCopy />,
description: "Increases the visual difference between elements",
parameters: {
factor: {
description: "Amount of contrast to add",
default: 100,
},
},
},
posterize: {
icon: <FaStairs />,
description: "Reduces image colors to distinct levels",
parameters: {
bits: {
description: "Number of bits to posterize to",
default: 2,
},
},
},
invert: {
icon: <FaFill />,
description: "Reverses colors in the image",
parameters: {},
},
shift: {
icon: <FaUncharted />,
description: "Shifts the image",
parameters: {
shift_x: {
description: "Amount to shift the image on the x axis",
default: 0.3,
},
shift_y: {
description: "Amount to shift the image on the y axis",
default: 0.3,
},
},
},
flip: {
icon: <FaArrowsUpDown />,
description: "Flips the image",
parameters: {},
},
mirror: {
icon: <FaArrowsLeftRight />,
description: "Mirror the image",
parameters: {},
},
grayscale: {
icon: <FaMoon />,
description: "Grayscale the image",
parameters: {},
},
solarize: {
// TODO: find a better icon :)
icon: <FaWaveSquare />,
description: "Solarize the image",
parameters: {
threshold: {
description: "Pixels above this greyscale level are inverted",
default: 128,
},
},
},
} as const
86 changes: 9 additions & 77 deletions frontend/src/util/recipe.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
import { ReactNode } from "react"
import {
FaArrowUpRightDots,
FaCopy,
FaFill,
FaGlasses,
FaStairs,
} from "react-icons/fa6"

export type Options = Record<string, unknown>

export type Ingredient = {
id: string
with?: Options
disabled?: boolean
}
import { Ingredient } from "@/util/ingredients"

/**
* A recipe is a collection of ingredients that can be applied to an image.
*/
export type Recipe = {
name: string
description: string
Expand All @@ -24,10 +12,13 @@ export type Recipe = {
preview: string
}

/**
* A list of recipes that can be applied to an image.
*/
export const recipes: Recipe[] = [
{
name: "Clean",
description: "A clean image with no modifications",
name: "No Recipe",
description: "No modifications",
destroy_factor: 0,
quality: 100,
ingredients: [],
Expand Down Expand Up @@ -71,62 +62,3 @@ export const recipes: Recipe[] = [
preview: "/examples/pro-plus.jpeg",
},
]

export type ParamInfo = {
description: string
default: unknown
}

export type IngredientMeta = {
icon: ReactNode
description: string
param_info: Record<string, ParamInfo>
}

export const ingredientMeta: Record<string, IngredientMeta> = {
exponential_noise: {
icon: <FaArrowUpRightDots />,
description: "Adds random variation",
param_info: {
scale: {
description: "Amount of noise to add",
default: 30,
},
},
},
sharpness: {
icon: <FaGlasses />,
description: "Enhances image edge definition",
param_info: {
factor: {
description: "Amount of sharpness to add",
default: 100,
},
},
},
contrast: {
icon: <FaCopy />,
description: "Increases the visual difference between elements",
param_info: {
factor: {
description: "Amount of contrast to add",
default: 100,
},
},
},
posterize: {
icon: <FaStairs />,
description: "Reduces image colors to distinct levels",
param_info: {
bits: {
description: "Number of bits to posterize to",
default: 2,
},
},
},
invert: {
icon: <FaFill />,
description: "Reverses colors in the image",
param_info: {},
},
} as const
31 changes: 31 additions & 0 deletions recipe_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ def action_exponential_noise(img: Image, options: dict) -> Image:
return ImageNamespace.fromarray(image_array)


def action_flip(img: Image, _: dict) -> Image:
return ImageOps.flip(img)


def action_grayscale(img: Image, _: dict) -> Image:
return ImageOps.grayscale(img)


def action_mirror(img: Image, _: dict) -> Image:
return ImageOps.mirror(img)


def action_solarize(img: Image, options: dict) -> Image:
return ImageOps.solarize(img, int(clamp(options.get('threshold', 128), 0, 255)))


def action_shift(img: Image, options: dict) -> Image:
shift_x = int(img.size[0] * options.get('shift_x', 0.3))
shift_y = int(img.size[1] * options.get('shift_y', 0.0))
Expand Down Expand Up @@ -70,5 +86,20 @@ def action_shift(img: Image, options: dict) -> Image:
"shift_x": [float, int],
"shift_y": [float, int]
}
},
"flip": {
"executor": action_flip,
},
"grayscale": {
"executor": action_grayscale,
},
"mirror": {
"executor": action_mirror,
},
"solarize": {
"executor": action_solarize,
"options": {
"threshold": int,
}
}
}

0 comments on commit d6d9164

Please sign in to comment.