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