Files
rojo/plugin/src/App/Components/ClassIcon.lua
2025-11-07 13:07:19 -08:00

161 lines
3.9 KiB
Lua

local StudioService = game:GetService("StudioService")
local AssetService = game:GetService("AssetService")
type CachedImageInfo = {
pixels: buffer,
size: Vector2,
}
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Packages.Roact)
local e = Roact.createElement
local EditableImage = require(Plugin.App.Components.EditableImage)
local imageCache: { [string]: CachedImageInfo } = {}
local function cloneBuffer(b: buffer): buffer
local newBuffer = buffer.create(buffer.len(b))
buffer.copy(newBuffer, 0, b)
return newBuffer
end
local function getImageSizeAndPixels(image: string): (Vector2, buffer)
local cachedImage = imageCache[image]
if not cachedImage then
local editableImage = AssetService:CreateEditableImageAsync(Content.fromUri(image))
local size = editableImage.Size
local pixels = editableImage:ReadPixelsBuffer(Vector2.zero, size)
imageCache[image] = {
pixels = pixels,
size = size,
}
return size, cloneBuffer(pixels)
end
return cachedImage.size, cloneBuffer(cachedImage.pixels)
end
local function getRecoloredClassIcon(className, color)
local iconProps = StudioService:GetClassIcon(className)
if iconProps and color then
--stylua: ignore
local success, editableImageSize, editableImagePixels = pcall(function(_iconProps: { [any]: any }, _color: Color3): (Vector2, buffer)
local size, pixels = getImageSizeAndPixels(_iconProps.Image)
local pixelsLen = buffer.len(pixels)
local minVal, maxVal = math.huge, -math.huge
for i = 0, pixelsLen, 4 do
if buffer.readu8(pixels, i + 3) == 0 then
continue
end
local pixelVal = math.max(
buffer.readu8(pixels, i),
buffer.readu8(pixels, i + 1),
buffer.readu8(pixels, i + 2)
)
minVal = math.min(minVal, pixelVal)
maxVal = math.max(maxVal, pixelVal)
end
local hue, sat, val = _color:ToHSV()
for i = 0, pixelsLen, 4 do
if buffer.readu8(pixels, i + 3) == 0 then
continue
end
local gIndex = i + 1
local bIndex = i + 2
local pixelVal = math.max(
buffer.readu8(pixels, i),
buffer.readu8(pixels, gIndex),
buffer.readu8(pixels, bIndex)
)
local newVal = val
if minVal < maxVal then
-- Remap minVal - maxVal to val*0.9 - val
newVal = val * (0.9 + 0.1 * (pixelVal - minVal) / (maxVal - minVal))
end
local newPixelColor = Color3.fromHSV(hue, sat, newVal)
buffer.writeu8(pixels, i, newPixelColor.R)
buffer.writeu8(pixels, gIndex, newPixelColor.G)
buffer.writeu8(pixels, bIndex, newPixelColor.B)
end
return size, pixels
end, iconProps, color)
if success then
iconProps.EditableImagePixels = editableImagePixels
iconProps.EditableImageSize = editableImageSize
end
end
return iconProps
end
local ClassIcon = Roact.PureComponent:extend("ClassIcon")
function ClassIcon:init()
self.state = {
iconProps = nil,
}
end
function ClassIcon:updateIcon()
local props = self.props
local iconProps = getRecoloredClassIcon(props.className, props.color)
self:setState({
iconProps = iconProps,
})
end
function ClassIcon:didMount()
self:updateIcon()
end
function ClassIcon:didUpdate(lastProps)
if lastProps.className ~= self.props.className or lastProps.color ~= self.props.color then
self:updateIcon()
end
end
function ClassIcon:render()
local iconProps = self.state.iconProps
if not iconProps then
return nil
end
return e(
"ImageLabel",
{
Size = self.props.size,
Position = self.props.position,
LayoutOrder = self.props.layoutOrder,
AnchorPoint = self.props.anchorPoint,
ImageTransparency = self.props.transparency,
Image = iconProps.Image,
ImageRectOffset = iconProps.ImageRectOffset,
ImageRectSize = iconProps.ImageRectSize,
BackgroundTransparency = 1,
},
if iconProps.EditableImagePixels
then e(EditableImage, {
size = iconProps.EditableImageSize,
pixels = iconProps.EditableImagePixels,
})
else nil
)
end
return ClassIcon