Reputation: 79549
I've an application that indexes the top 16 colors that appear in videos.
I'm trying to write another application that allows the user to select a color and then the application finds all videos that this color appears in.
The problem is that as I only index 16 colors per video, the users choose an RGB color. The probability that this color is indexed is very low, so almost always my application returns no results.
I thought of a way I could make this work - I could index the colors that appear in the video and convert them to closest 8-bit color.
Then when a user selects an RGB color, I could convert the user choice to the same 8-bit closest color.
This way I'd always have matches.
The only major problem I've right now is how to convert an RGB color to the closest 8 bit color?
Upvotes: 15
Views: 46729
Reputation: 1
I have written a hex to 8 bit online tool. Here is the core code for the tool, code is very easy.
"use client";
import React, { useState } from "react";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
const hexTo8Bit = (hex: string) => {
const bigint = parseInt(hex.slice(1), 16);
const r = (bigint >> 16) & 255;
const g = (bigint >> 8) & 255;
const b = bigint & 255;
return `${r},${g},${b}`;
};
const bit8ToHex = (bit8: string) => {
const [r = 0, g = 0, b = 0] = bit8.split(",").map(Number);
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()}`;
};
export const Tool = () => {
const [hex, setHex] = useState("");
const [bit8, setBit8] = useState("");
const handleHexChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setHex(e.target.value);
};
const handleBit8Change = (e: React.ChangeEvent<HTMLInputElement>) => {
setBit8(e.target.value);
};
const handleHexBlur = () => {
setBit8(hexTo8Bit(hex));
};
const handleBit8Blur = () => {
setHex(bit8ToHex(bit8));
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
if (e.currentTarget.name === "hex") {
setBit8(hexTo8Bit(hex));
} else if (e.currentTarget.name === "bit8") {
setHex(bit8ToHex(bit8));
}
}
};
const handleConvert = () => {
setBit8(hexTo8Bit(hex));
setHex(bit8ToHex(bit8));
};
return (
<div className="grid w-full gap-8 rounded-md bg-muted p-4">
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
HEX code:
<Input
name="hex"
value={hex}
onChange={handleHexChange}
onBlur={handleHexBlur}
onKeyDown={handleKeyDown}
/>
</div>
<div className="grid gap-2">
8 Bit:
<Input
name="bit8"
value={bit8}
onChange={handleBit8Change}
onBlur={handleBit8Blur}
onKeyDown={handleKeyDown}
/>
</div>
</div>
<Button className="mx-auto w-1/2" onClick={handleConvert}>
Convert
</Button>
</div>
);
};
Upvotes: 0
Reputation: 784
tmux has a logic to convert 24bit RGB to 256 colors. It uses closer one from closest 6x6x6 or closest grey. https://github.com/tmux/tmux/blob/dae2868d1227b95fd076fb4a5efa6256c7245943/colour.c#L57
Upvotes: 3
Reputation: 154
Here is a Very useful Python Script that I found a few years back. All credit goes to Micah Elliott.
.py
file with the desired name.Run the program along with arguements like this: python file.py 9CE445
to get the closest 8-bit color value and python file.py 204
to get the exact Hex value.
""" Convert values between RGB hex codes and xterm-256 color codes.
Nice long listing of all 256 colors and their codes. Useful for
developing console color themes, or even script output schemes.
Resources:
* http://en.wikipedia.org/wiki/8-bit_color
* http://en.wikipedia.org/wiki/ANSI_escape_code
* /usr/share/X11/rgb.txt
I'm not sure where this script was inspired from. I think I must have
written it from scratch, though it's been several years now.
"""
__author__ = 'Micah Elliott http://MicahElliott.com'
__version__ = '0.1'
__copyright__ = 'Copyright (C) 2011 Micah Elliott. All rights reserved.'
__license__ = 'WTFPL http://sam.zoy.org/wtfpl/'
#---------------------------------------------------------------------
import sys, re
CLUT = [ # color look-up table
# 8-bit, RGB hex
# Primary 3-bit (8 colors). Unique representation!
('00', '000000'),
('01', '800000'),
('02', '008000'),
('03', '808000'),
('04', '000080'),
('05', '800080'),
('06', '008080'),
('07', 'c0c0c0'),
# Equivalent "bright" versions of original 8 colors.
('08', '808080'),
('09', 'ff0000'),
('10', '00ff00'),
('11', 'ffff00'),
('12', '0000ff'),
('13', 'ff00ff'),
('14', '00ffff'),
('15', 'ffffff'),
# Strictly ascending.
('16', '000000'),
('17', '00005f'),
('18', '000087'),
('19', '0000af'),
('20', '0000d7'),
('21', '0000ff'),
('22', '005f00'),
('23', '005f5f'),
('24', '005f87'),
('25', '005faf'),
('26', '005fd7'),
('27', '005fff'),
('28', '008700'),
('29', '00875f'),
('30', '008787'),
('31', '0087af'),
('32', '0087d7'),
('33', '0087ff'),
('34', '00af00'),
('35', '00af5f'),
('36', '00af87'),
('37', '00afaf'),
('38', '00afd7'),
('39', '00afff'),
('40', '00d700'),
('41', '00d75f'),
('42', '00d787'),
('43', '00d7af'),
('44', '00d7d7'),
('45', '00d7ff'),
('46', '00ff00'),
('47', '00ff5f'),
('48', '00ff87'),
('49', '00ffaf'),
('50', '00ffd7'),
('51', '00ffff'),
('52', '5f0000'),
('53', '5f005f'),
('54', '5f0087'),
('55', '5f00af'),
('56', '5f00d7'),
('57', '5f00ff'),
('58', '5f5f00'),
('59', '5f5f5f'),
('60', '5f5f87'),
('61', '5f5faf'),
('62', '5f5fd7'),
('63', '5f5fff'),
('64', '5f8700'),
('65', '5f875f'),
('66', '5f8787'),
('67', '5f87af'),
('68', '5f87d7'),
('69', '5f87ff'),
('70', '5faf00'),
('71', '5faf5f'),
('72', '5faf87'),
('73', '5fafaf'),
('74', '5fafd7'),
('75', '5fafff'),
('76', '5fd700'),
('77', '5fd75f'),
('78', '5fd787'),
('79', '5fd7af'),
('80', '5fd7d7'),
('81', '5fd7ff'),
('82', '5fff00'),
('83', '5fff5f'),
('84', '5fff87'),
('85', '5fffaf'),
('86', '5fffd7'),
('87', '5fffff'),
('88', '870000'),
('89', '87005f'),
('90', '870087'),
('91', '8700af'),
('92', '8700d7'),
('93', '8700ff'),
('94', '875f00'),
('95', '875f5f'),
('96', '875f87'),
('97', '875faf'),
('98', '875fd7'),
('99', '875fff'),
('100', '878700'),
('101', '87875f'),
('102', '878787'),
('103', '8787af'),
('104', '8787d7'),
('105', '8787ff'),
('106', '87af00'),
('107', '87af5f'),
('108', '87af87'),
('109', '87afaf'),
('110', '87afd7'),
('111', '87afff'),
('112', '87d700'),
('113', '87d75f'),
('114', '87d787'),
('115', '87d7af'),
('116', '87d7d7'),
('117', '87d7ff'),
('118', '87ff00'),
('119', '87ff5f'),
('120', '87ff87'),
('121', '87ffaf'),
('122', '87ffd7'),
('123', '87ffff'),
('124', 'af0000'),
('125', 'af005f'),
('126', 'af0087'),
('127', 'af00af'),
('128', 'af00d7'),
('129', 'af00ff'),
('130', 'af5f00'),
('131', 'af5f5f'),
('132', 'af5f87'),
('133', 'af5faf'),
('134', 'af5fd7'),
('135', 'af5fff'),
('136', 'af8700'),
('137', 'af875f'),
('138', 'af8787'),
('139', 'af87af'),
('140', 'af87d7'),
('141', 'af87ff'),
('142', 'afaf00'),
('143', 'afaf5f'),
('144', 'afaf87'),
('145', 'afafaf'),
('146', 'afafd7'),
('147', 'afafff'),
('148', 'afd700'),
('149', 'afd75f'),
('150', 'afd787'),
('151', 'afd7af'),
('152', 'afd7d7'),
('153', 'afd7ff'),
('154', 'afff00'),
('155', 'afff5f'),
('156', 'afff87'),
('157', 'afffaf'),
('158', 'afffd7'),
('159', 'afffff'),
('160', 'd70000'),
('161', 'd7005f'),
('162', 'd70087'),
('163', 'd700af'),
('164', 'd700d7'),
('165', 'd700ff'),
('166', 'd75f00'),
('167', 'd75f5f'),
('168', 'd75f87'),
('169', 'd75faf'),
('170', 'd75fd7'),
('171', 'd75fff'),
('172', 'd78700'),
('173', 'd7875f'),
('174', 'd78787'),
('175', 'd787af'),
('176', 'd787d7'),
('177', 'd787ff'),
('178', 'd7af00'),
('179', 'd7af5f'),
('180', 'd7af87'),
('181', 'd7afaf'),
('182', 'd7afd7'),
('183', 'd7afff'),
('184', 'd7d700'),
('185', 'd7d75f'),
('186', 'd7d787'),
('187', 'd7d7af'),
('188', 'd7d7d7'),
('189', 'd7d7ff'),
('190', 'd7ff00'),
('191', 'd7ff5f'),
('192', 'd7ff87'),
('193', 'd7ffaf'),
('194', 'd7ffd7'),
('195', 'd7ffff'),
('196', 'ff0000'),
('197', 'ff005f'),
('198', 'ff0087'),
('199', 'ff00af'),
('200', 'ff00d7'),
('201', 'ff00ff'),
('202', 'ff5f00'),
('203', 'ff5f5f'),
('204', 'ff5f87'),
('205', 'ff5faf'),
('206', 'ff5fd7'),
('207', 'ff5fff'),
('208', 'ff8700'),
('209', 'ff875f'),
('210', 'ff8787'),
('211', 'ff87af'),
('212', 'ff87d7'),
('213', 'ff87ff'),
('214', 'ffaf00'),
('215', 'ffaf5f'),
('216', 'ffaf87'),
('217', 'ffafaf'),
('218', 'ffafd7'),
('219', 'ffafff'),
('220', 'ffd700'),
('221', 'ffd75f'),
('222', 'ffd787'),
('223', 'ffd7af'),
('224', 'ffd7d7'),
('225', 'ffd7ff'),
('226', 'ffff00'),
('227', 'ffff5f'),
('228', 'ffff87'),
('229', 'ffffaf'),
('230', 'ffffd7'),
('231', 'ffffff'),
# Gray-scale range.
('232', '080808'),
('233', '121212'),
('234', '1c1c1c'),
('235', '262626'),
('236', '303030'),
('237', '3a3a3a'),
('238', '444444'),
('239', '4e4e4e'),
('240', '585858'),
('241', '626262'),
('242', '6c6c6c'),
('243', '767676'),
('244', '808080'),
('245', '8a8a8a'),
('246', '949494'),
('247', '9e9e9e'),
('248', 'a8a8a8'),
('249', 'b2b2b2'),
('250', 'bcbcbc'),
('251', 'c6c6c6'),
('252', 'd0d0d0'),
('253', 'dadada'),
('254', 'e4e4e4'),
('255', 'eeeeee'),
]
def _str2hex(hexstr):
return int(hexstr, 16)
def _strip_hash(rgb):
# Strip leading `#` if exists.
if rgb.startswith('#'):
rgb = rgb.lstrip('#')
return rgb
def _create_dicts():
short2rgb_dict = dict(CLUT)
rgb2short_dict = {}
for k, v in short2rgb_dict.items():
rgb2short_dict[v] = k
return rgb2short_dict, short2rgb_dict
def short2rgb(short):
return SHORT2RGB_DICT[short]
def print_all():
""" Print all 256 xterm color codes.
"""
for short, rgb in CLUT:
sys.stdout.write('\033[48;5;%sm%s:%s' % (short, short, rgb))
sys.stdout.write("\033[0m ")
sys.stdout.write('\033[38;5;%sm%s:%s' % (short, short, rgb))
sys.stdout.write("\033[0m\n")
print ("Printed all codes.")
print ("You can translate a hex or 0-255 code by providing an argument.")
def rgb2short(rgb):
""" Find the closest xterm-256 approximation to the given RGB value.
@param rgb: Hex code representing an RGB value, eg, 'abcdef'
@returns: String between 0 and 255, compatible with xterm.
>>> rgb2short('123456')
('23', '005f5f')
>>> rgb2short('ffffff')
('231', 'ffffff')
>>> rgb2short('0DADD6') # vimeo logo
('38', '00afd7')
>>> rgb2short('3D3D3D')
('237', '3a3a3a')
>>> rgb2short('070707')
('232', '080808')
"""
rgb = _strip_hash(rgb)
# Break 6-char RGB code into 3 integer vals.
parts = [ int(h, 16) for h in re.split(r'(..)(..)(..)', rgb)[1:4] ]
incs = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
if parts[0] == parts[1] == parts[2]:
gs_incs = range(0x08, 0xee, 10)
incs = sorted(incs + gs_incs + [0xee,])
res = []
for part in parts:
i = 0
while i < len(incs)-1:
s, b = incs[i], incs[i+1] # smaller, bigger
if s <= part <= b:
s1 = abs(s - part)
b1 = abs(b - part)
if s1 < b1: closest = s
else: closest = b
res.append(closest)
break
i += 1
#print '***', res
res = ''.join([ ('%02.x' % i) for i in res ])
equiv = RGB2SHORT_DICT[ res ]
#print '***', res, equiv
return equiv, res
RGB2SHORT_DICT, SHORT2RGB_DICT = _create_dicts()
#---------------------------------------------------------------------
if __name__ == '__main__':
import doctest
doctest.testmod()
if len(sys.argv) == 1:
print_all()
raise SystemExit
arg = sys.argv[1]
if len(arg) < 4 and int(arg) < 256:
rgb = short2rgb(arg)
sys.stdout.write('xterm color \033[38;5;%sm%s\033[0m -> RGB exact \033[38;5;%sm%s\033[0m' % (arg, arg, arg, rgb))
sys.stdout.write("\033[0m\n")
else:
short, rgb = rgb2short(arg)
sys.stdout.write('RGB %s -> xterm color approx \033[38;5;%sm%s (%s)' % (arg, short, short, rgb))
sys.stdout.write("\033[0m\n")
Upvotes: 2
Reputation: 1
Try this algorithm if you want to convert 24 bpp image to 8 bpp image:
for y = 0 to ImageHeight - 1
for x = 0 to ImageWidth - 1
GetPixel(x,y,red,grn,blu)
{read RGB data from 24bpp file}
d0 = red^2 + grn^2 + blu^2
ColorIndex = 0
for cl = 0 to 255
GetPaletteData(p_red,p_gre,p_blu)
{read RGB data from 8bpp palette}
d = (red - p_red)^2 + (grn - p_grn)^2 + (blu - p_blu)^2
if d0 >= d then
ColorIndex = cl
d0 = d
end if
next cl
{use ColorIndex to create your 8bpp file}
next x
next y
Before this step, read more about 8bpp files from Wikipedia or other sources.
Good luck!
Upvotes: 0
Reputation: 117
One possibility is to simply scale your 24-bit color down into an 8-bit color space. As cHao mentions, you could use RRRGGGBB for your 8-bit number. Then each color component can be calculated by a simple scaling algorithm such as:
byte red = (originalColor.red * 8) / 256;
byte green = (originalColor.green * 8) / 256;
byte blue = (originalColor.blue * 4) / 256;
The 8, 4, and 254 are the number of possible values in each color component. In your original 24-bit color, red, green, and blue can all have 256 possible values, so that is the divisor of the scaling equation. In the example 8-bit color, red and green are each 3 bits (8 possible values) and blue is 2 bits (4 possible values).
After you get these three components, you can combine them with some simple bit shift arithmetic:
byte eightBitColor = (red << 5) | (green << 2) | blue;
Then you can simply compare these 8-bit colors. Their drastically reduced resolution may help you.
Alternately, you can do something like Tyler suggested, and convert to HSB or HSV first, and only compare hues (depending on whether or not you need brightness and saturation information). Depending on your goal, that may actually be a more ideal solution.
Edit: Modified scaling algorithm to fix a shortcoming pointed out by Mark Ransom.
Upvotes: 3
Reputation: 308530
To convert to the web-safe palette, you need to convert the range of each of the r,g,b components from 0-255 to 0-5 and combine them:
color = (r*6/256)*36 + (g*6/256)*6 + (b*6/256)
Upvotes: 13
Reputation: 11562
What you need to do is convert the RGB to an HSB (hue saturation brightness) value. HSB is 3 bytes, just like RGB, the difference is that HSB values can be compared much more easily than RGB.
Your next step is decide on an "importance" weighting. For example, if all you care about is "color/hue", not saturation or brightness, then you can throw away the S and B bytes and just use the color byte.
If it were me and I were constrained to 8 bits I would use 4 bits of color information (16 different colors), 3 bits of saturation (8 different values), and 1 bit of brightness information (light or dark).
This article describes how to do HSB in Java:
http://java.sys-con.com/node/43559
The source code for this article has an RGB to HSB converter in Java.
Upvotes: 6
Reputation: 935
Are you familiar with Floyd–Steinberg dithering? This is used to convert higher order colors to lower order colors, e.g. 24 bit RGB to 3 bit (8 colors) RGB or restricting an RGB image to 8 bits (256 colors) for a GIF conversion.
This algorithm is described on the linked wikipedia page.
Upvotes: 0