Задача: используя наименьшее возможное количество ресурсов, отрендерить осмысленный текст.
- Насколько маленьким может быть читаемый шрифт?
- Сколько памяти понадобится, чтобы его хранить?
- Сколько кода понадобится, чтобы его использовать?
Посмотрим, что у нас получится. Спойлер:
Введение в битмэпы
Компьютеры представляют растровые изображения в виде битмэпов. Речь не о формате .bmp
, а о способе хранения пикселей в памяти. Для понимания происходящего нам надо кое-что про этот способ узнать.
Слои
Изображение обычно содержит несколько слоёв, расположенных один поверх другого. Чаще всего они соответствуют координатам цветового пространства RGB. Один слой для красного, один для зелёного и один для синего. Если формат изображения поддерживает прозрачность, то для неё создаётся четвёртый слой, обычно называемый альфа. Грубо говоря, цветное изображение — это три (или четыре, если есть альфа-канал) чёрно-белых, расположенных одно над другим.
- RGB — не единственное цветовое пространство; формат JPEG, например, использует YUV. Но в этой статье остальные цветовые пространства нам не понадобятся, поэтому мы их не рассматриваем.
Набор слоёв может быть представлен в памяти двумя способами. Либо они хранятся по отдельности, либо значения из разных слоёв перемежаются. В последнем случае слои называются каналами, и именно так устроено большинство современных форматов.
Допустим, у нас есть рисунок 4x4, содержащий три слоя: R для красного, G для зелёного и B для синего компонента каждого из пикселей. Он может быть представлен вот так:
R R R R
R R R R
R R R R
R R R R
G G G G
G G G G
G G G G
G G G G
B B B B
B B B B
B B B B
B B B B
Все три слоя хранятся по отдельности. Перемежающийся формат выглядит иначе:
RGB RGB RGB RGB
RGB RGB RGB RGB
RGB RGB RGB RGB
RGB RGB RGB RGB
- каждая тройка символов соответствует ровно одному пикселю
- значения в пределах тройки идут в порядке RGB. Иногда может использоваться и другой порядок (например, BGR), но этот — самый распространённый.
Для простоты я расположил пиксели в виде двухмерной матрицы, потому что так понятнее, где в изображении находится та или иная тройка. Но на самом деле память компьютера не двумерная, а одномерная, поэтому рисунок 4х4 будет храниться вот так:
RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB
bpp
Аббревиатурой bpp обозначается количество битов или байт на пиксель (bits/bytes per pixel). Вам могло где-то попадаться на глаза 24bpp
или 3bpp
. Эти две характеристики означают одно и то же — 24 бита на пиксель или 3 байта на пиксель. Так как в байте всегда 8 бит, по величине значения можно догадаться, о какой из единиц идёт речь.
Представление в памяти
24bpp
, он же 3bpp
— самый распостранённый формат для хранения цветов. Вот так на уровне отдельных битов выглядит один пиксель в порядке RGB.
бит 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
пиксель R R R R R R R R G G G G G G G G B B B B B B B B
- Один байт для R, один для G и один для B, итого три байта.
- Каждый из них содержит значение от 0 до 255.
Так что если данный пиксель имеет следующий цвет:
R 255
G 80
B 100
Тогда в первом байте хранится 255
, во втором 80
, а в третьем — 100
.
Чаще всего эти значения представляются в шестнадцатеричном виде. Скажем, #ff5064
. Так гораздо удобнее и компактнее: R = 0xff
(т.е. R=255
в десятеричном представлении), G = 0x50
(=G=80
), B=0x64
(=B=100
).
- У шестнадцатеричного представления есть одно полезное свойство. Так как каждый байт цвета представлен двумя символами, каждый символ кодирует ровно пол-байта, или четыре бита. 4 бита, кстати, называются ниббл.
Ширина строки
Когда пиксели идут один за другим и каждый содержит больше одного канала, в данных легко запутаться. Неизвестно, когда заканчивается одна строка и начинается следующая, поэтому для интерпретации файла с битмэпом нужно знать размер изображения и bpp. В нашем случае рисунок имеет ширину w = 4
пикселя и каждый из этих пикселей содержит 3 байта, поэтому строка кодируется 12 (в общем случае w*bpp
) байтами.
- Строка не всегда кодируется ровно
w*bpp
байтами; часто в неё добавляются "скрытые" пиксели, чтобы довести ширину изображения до какой-то величины. Например, масштабировать рисунки быстрее и удобнее, когда их размер в пикселях равен степени двойки. Поэтому файл может содержать (доступное пользователю) изображение 120х120 пикселей, но храниться как изображение 128х128. При выводе изображения на экран эти пиксели игнорируются. Впрочем, нам о них знать не обязательно.
Координата любого пикселя (x, y)
в одномерном представлении — (y * w + x) * bpp
. Это, в общем-то, очевидно: y
— номер строки, каждая строка содержит w
пикселей, так что y * w
— это начало нужной строки, а+x
переносит нас к нужному x
в её пределах. А так как координаты не в байтах, а в пикселях, всё это умножается на размер пикселя bpp
, в данном случае в байтах. Так как пиксель имеет ненулевой размер, нужно прочитать ровно bpp
байт, начиная с полученной координаты, и у нас будет полное представление нужного пикселя.
Атлас шрифта
Реально существующие мониторы отображают не пиксель как единое целое, а три субпикселя — красный, синий и зелёный. Если посмотреть на монитор под увеличением, то вы увидите что-то вроде вот этого:
- Рисунок с википедии
Нас интересует LCD, так как, скорее всего, именно с такого монитора вы и читаете этот текст. Разумеется, есть подводные камни:
- Не все матрицы используют именно такой порядок субпикселей, бывает и BGR.
- Если повернуть монитор (например, смотреть на телефоне в альбомной ориентации), то паттерн тоже повернётся и шрифт перестанет работать.
- Разные ориентации матрицы и расположение субпикселей потребуют переделки самого шрифта.
- В частности, он не работает на AMOLED-дисплеях, использующих расположение PenTile. Такие дисплеи чаще всего используются в мобильных устройствах.
Использование связанных с субпикселями хаков для увеличения разрешения называется субпиксельным рендерингом. О его применении в типографике можно почитать, например, здесь.
К счастью для нас, Мэтт Сарнов уже догадался использовать субпиксельный рендеринг для создания крошечного шрифта millitext. Вручную он создал вот эту крошечную картинку:
Которая, если очень внимательно вглядываться в монитор, выглядит вот так:
А вот она же, программно увеличенная в 12 раз:
Отталкиваясь от его работы, я создал атлас шрифта, в котором каждому символу соответствует колонка 1x5
пикселей. Порядок символов следующий:
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
Тот же атлас, увеличенный в 12 раз:
С 36 используемыми символами получается ровно 36х5
пикселей. Если считать, что каждый пиксель занимает 3 байта, то нам нужно 36*5*3 = 540
байт на то, чтобы хранить весь рисуонк (прим. пер.: в оригинале запутанная серия правок по поводу альфа-канала, удаления метаданных и т.п. В переводе я её опустил и использую только окончательный вариант файла). PNG-файл, пропущенный через pngcrush и optipng, занимает даже меньше:
# wc -c < font-crushed.png
390
Но можно добиться ещё меньшего размера, если использовать слегка другой подход
Сжатие
Внимательный читатель мог заметить, что атлас использует всего 7 цветов:
#ffffff
#ff0000
#00ff00
#0000ff
#00ffff
#ff00ff
#ffff00
Палитра
В таких ситуациях часто проще создать палитру. Тогда для каждого пикселя можно хранить не три байта цвета, а только номер цвета в палитре. В нашем случае для выбора из 7 цветов будет достаточно 3 бит (7 < 2^3
). Если каждому пикселю сопоставить трёхбитное значение, то весь атлас поместится в 68 байт.
- Читатель, разбирающийся в сжатии данных, может ответить, что вообще-то есть такая штука как "дробные биты" и в нашем случае достаточно 2.875 бит на пиксель. Такой плотности можно добиться с помощью чёрной магии, известной как арифметическое кодирование. Мы этого делать не будем, потому что арифметическое кодирование — сложная штука, а 68 байт — это и так немного.
Выравнивание
У трёхбитного кодирования есть один серьёзный недостаток. Пиксели не могут быть равномерно распределены по 8-битным байтам, что важно, потому что байт — минимальный адресуемый участок памяти. Допустим, мы хотим сохранить три пикселя:
A B C
Если каждый занимает 3 бита, то на их хранение уйдёт 2 байта (-
обозначает неиспользуемые биты):
bit 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
pixel A A A B B B C C C - - - - - - -
Что важно, пиксель C не просто оставляет кучу пустого места; он разорван между двумя байтами. Когда мы начнём добавлять следующие пиксели, они могут быть расположены как угодно относительно границ байтов. Простейшим решением будет использовать по нибблу на пиксель, потому что 8 прекрасно делится на 4 и позволяет помещать в каждый байт ровно по два пикселя. Но это увеличит размер атласа на треть, с 68 байт до 90 байт.
- На самом деле файл можно сделать ещё меньше, используя кодирование палиндромов, интервальное кодирование и другие методики сжатия. Как и арифметическое кодирование, эти методики мы отложим до следующей статьи.
Битовый буфер
К счастью, в работе с 3-битными значениями нет ничего принципиально невозможного. Нужно просто следить за тем, какую позицию внутри байта мы пишем или читаем в данный момент. Следующий простенький класс конвертирует 3-битный поток данных в массив байтов.
- Из соображений читаемости код написан на JS, но тот же метод генерализуется на другие языки.
- Используется порядок от младшего байта к старшему (Little Endian)
class BitBuffer {
constructor(bytes) {
this.data = new Uint8Array(bytes);
this.offset = 0;
}
write(value) {
for (let i = 0; i < 3; ) {
// bits remaining
const remaining = 3 - i;
// bit offset in the byte i.e remainder of dividing by 8
const bit_offset = this.offset & 7;
// byte offset for a given bit offset, i.e divide by 8
const byte_offset = this.offset >> 3;
// max number of bits we can write to the current byte
const wrote = Math.min(remaining, 8 - bit_offset);
// mask with the correct bit-width
const mask = ~(0xff << wrote);
// shift the bits we want to the start of the byte and mask off the rest
const write_bits = value & mask;
// destination mask to zero all the bits we're changing first
const dest_mask = ~(mask << bit_offset);
value >>= wrote;
// write it
this.data[byte_offset] = (this.data[byte_offset] & dest_mask) | (write_bits << bit_offset);
// advance
this.offset += wrote;
i += wrote;
}
}
to_string() {
return Array.from(this.data, (byte) => ('0' + (byte & 0xff).toString(16)).slice(-2)).join('');
}
};
Давайте загрузим и закодируем файл с атласом:
const PNG = require('png-js');
const fs = require('fs');
// this is our palette of colors
const Palette = [
[0xff, 0xff, 0xff],
[0xff, 0x00, 0x00],
[0x00, 0xff, 0x00],
[0x00, 0x00, 0xff],
[0x00, 0xff, 0xff],
[0xff, 0x00, 0xff],
[0xff, 0xff, 0x00]
];
// given a color represented as [R, G, B], find the index in palette where that color is
function find_palette_index(color) {
const [sR, sG, sB] = color;
for (let i = 0; i < Palette.length; i++) {
const [aR, aG, aB] = Palette[i];
if (sR === aR && sG === aG && sB === aB) {
return i;
}
}
return -1;
}
// build the bit buffer representation
function build(cb) {
const data = fs.readFileSync('subpixels.png');
const image = new PNG(data);
image.decode(function(pixels) {
// we need 3 bits per pixel, so w*h*3 gives us the # of bits for our buffer
// however BitBuffer can only allocate bytes, dividing this by 8 (bits for a byte)
// gives us the # of bytes, but that division can result in 67.5 ... Math.ceil
// just rounds up to 68. this will give the right amount of storage for any
// size atlas.
let result = new BitBuffer(Math.ceil((image.width * image.height * 3) / 8));
for (let y = 0; y < image.height; y++) {
for (let x = 0; x < image.width; x++) {
// 1D index as described above
const index = (y * image.width + x) * 4;
// extract the RGB pixel value, ignore A (alpha)
const color = Array.from(pixels.slice(index, index + 3));
// write out 3-bit palette index to the bit buffer
result.write(find_palette_index(color));
}
}
cb(result);
});
}
build((result) => console.log(result.to_string()));
Как и ожидалось, атлас поместился в 68 байт, что в 6 раз меньше PNG-файла.
(прим. пер.: автор несколько лукавит: он не сохранил палитру и размер изображения, что по моим прикидкам потребует 23 байт при фиксированном размере палитры и увеличит размер изображения до 91 байта)
Теперь давайте сконвертируем изображение в строку, чтобы можно было вставить его в исходный код. По сути, метод to_string
это и делает: он представляет содержимое каждого байта в виде шестнадцатеричного числа.
305000000c0328d6d4b24cb46d516d4ddab669926a0ddab651db76150060009c0285
e6a0752db59054655bd7b569d26a4ddba053892a003060400d232850b40a6b61ad00
Но получившаяся строка всё ещё довольно длинная, потому что мы ограничили себя алфавитом из 16 символов. Можно заменить его на base64, в котором символов вчетверо больше.
to_string() {
return Buffer.from(this.data).toString('base64');
}
В base64 атлас выглядит вот так:
MFAAAAwDKNbUsky0bVFtTdq2aZJqDdq2Udt2FQBgAJwCheagdS21kFRlW9e1adJqTdugU4kqADBgQA0jKFC0CmthrQA=
Эту строку можно захардкодить в JS-модуль и использовать для растеризации текста.
Растеризация
Для экономии памяти в каждый момент будет декодироваться только одна буква.
const Alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const Atlas = Uint8Array.from(Buffer.from('MFAAAAwDKNbUsky0bVFtTdq2aZJqDdq2Udt2FQBgAJwCheagdS21kFRlW9e1adJqTdugU4kqADBgQA0jKFC0CmthrQA=', 'base64'));
const Palette = [
[0xff, 0xff, 0xff],
[0xff, 0x00, 0x00],
[0x00, 0xff, 0x00],
[0x00, 0x00, 0xff],
[0x00, 0xff, 0xff],
[0xff, 0x00, 0xff],
[0xff, 0xff, 0x00]
];
// at the given bit offset |offset| read a 3-bit value from the Atlas
read = (offset) => {
let value = 0;
for (let i = 0; i < 3; ) {
const bit_offset = offset & 7;
const read = Math.min(3 - i, 8 - bit_offset);
const read_bits = (Atlas[offset >> 3] >> bit_offset) & (~(0xff << read));
value |= read_bits << i;
offset += read;
i += read;
}
return value;
};
// for a given glyph |g| unpack the palette indices for the 5 vertical pixels
unpack = (g) => {
return (new Uint8Array(5)).map((_, i) =>
read(Alphabet.length*3*i + Alphabet.indexOf(g)*3));
};
// for given glyph |g| decode the 1x5 vertical RGB strip
decode = (g) => {
const rgb = new Uint8Array(5*3);
unpack(g).forEach((value, index) =>
rgb.set(Palette[value], index*3));
return rgb;
}
Функция decode
принимает на вход символ и возвращает соответствующий столбец исходного изображения. Впечатляет тут то, что на декодирование одного символа уходит всего 5 байт памяти, плюс ~1.875 байт на то, чтобы прочитать нужный кусок массива, т.е. в среднем 6.875 на букву. Если прибавить 68 байт на хранение массива и 36 байт на хранение алфавита, то получится, что теоретически можно рендерить текст со 128 байтами RAM.
- Такое возможно, если переписать код на C или ассемблере. На фоне оверхеда JS это экономия на спичках.
Осталось только собрать эти столбцы в единое целое и вернуть картинку с текстом.
print = (t) => {
const c = t.toUpperCase().replace(/[^\w\d ]/g, '');
const w = c.length * 2 - 1, h = 5, bpp = 3; // * 2 for whitespace
const b = new Uint8Array(w * h * bpp);
[...c].forEach((g, i) => {
if (g !== ' ') for (let y = 0; y < h; y++) {
// copy each 1x1 pixel row to the the bitmap
b.set(decode(g).slice(y * bpp, y * bpp + bpp), (y * w + i * 2) * bpp);
}
});
return {w: w, h: h, data: b};
};
Это и будет минимальный возможный шрифт.
const fs = require('fs');
const result = print("Breaking the physical limits of fonts");
fs.writeFileSync(`${result.w}x${result.h}.bin`, result.data);
Добавим немножко imagemagick, чтоб получить изображение в читаемом формате:
# convert -size 73x5 -depth 8 rgb:73x5.bin done.png
И вот финальный результат:
Оно же, увеличенное в 12 раз:
Оно же, снятое макросъёмкой с плохо откалиброванного монитора:
И наконец, оно же на мониторе получше:
Комментарии (63)
KvanTTT
19.07.2019 17:49+1Которая, если очень внимательно вглядываться в монитор, выглядит вот так:
Я бы сказал, что для этого нужно смотреть в монитор под лупой.
synedra Автор
19.07.2019 18:15+3У меня получалось разобрать отдельные глифы невооружённым взглядом. Зависит от разрешения монитора, наверное.
DistortNeo
19.07.2019 22:15+1Ага. Попытался — вообще без шансов. Монитор 4K, 140 ppi. Невооружённым глазом субпиксели вообще не видно — потому всем так ненавистный ClearType работает просто замечательно. Только при макросъёмке при примерно с 10 попытки получилось сделать чёткий кадр.
Moskus
19.07.2019 23:06+1На экране с высоким разрешением (телефон HD, настольный монитор 4k, Retina) или мониторе с матовым покрытием невооруженным глазом такое прочитать, практически, невозможно.
pda0
19.07.2019 20:14+1Так вот что там разглядывал Бастрыкин! А мы-то, дураки, зря потешались над ним…
stalinets
19.07.2019 19:21+7Самые мелкие шрифты, с которыми я сталкивался в реале (помимо условий кредитов =)) — это шрифты из J2ME приложений для мобильников середины 2000-х годов. У меня, например, был Siemens C65, с экранчиком 130*130 пикселей, и вот такие шрифты были на нём вполне читабельными (хотя и считались «мелкими»):
Эти картинки и есть шрифт, я их сейчас вытащил из тех самых .jar файлов, которые запускал на своём сименсе в 2005-м.
Такой шрифт можно было установить в ридере для книг, или добавить в конструкторе для Jimm ICQ.mistergrim
19.07.2019 19:53С 36 используемыми символами получается ровно 36х5 пикселей. Если считать, что каждый пиксель занимает 3 байта, то нам нужно 36*5*3 = 540 байт на то, чтобы хранить весь рисуонк
Ну и зачем это? Если можно сделать нормальный шрифт 8?5, в котором каждый символ будет занимать 5 байтов.
Имхо, идея интересна лишь тем, как впихнуть на экран низкого разрешения как можно больше символов.synedra Автор
19.07.2019 20:07Незачем, помимо эстетической ценности. Я даже представить себе не могу таких обстоятельств, когда надо будет настолько экономить байты, но при этом будет доступен многоцветный LCD-экранчик.
ardraeiss
20.07.2019 21:31Например, если делать в стиле «ностальгии»/«ретро». Есть же сейчас мода на «пиксельные игры», где под капотом нормальные SDL или 3D сидят.
Взять такой шрифт, порендерить квадратами, занести результат на экране модели условно древней мобилки — и готов элемент окружения «а-ля нулевые» для какой-нибудь игры, причём вполне допускающий осмысленную сюжетную интерактивность(кто сказал «Steins;Gate»?).
cheburen
20.07.2019 16:01Это неплохой шрифт для стеганографии, при вертикальной записи, простая линия на сайте превращается в текст при внимательном рассмотрении.
mistergrim
20.07.2019 16:02+2Смысл стеганографии в том, чтобы она и при внимательном рассмотрении была необнаружима.
engine9
19.07.2019 23:41Ох, такую ностальгию вызвали! Помню, была софтина, позволявшая много-много текста запихнуть в приложение и потом его довольно комфортно просматривать. Я так шпоры делал.
Alexey2005
19.07.2019 21:30+2Англоговорящим-то хорошо, у них буквы компактные. А вот в русском есть такие монстры, как например Ы, Щ, Ж, Ю, Д, которые ещё попробуй вмести даже в 4x8, чтоб оно не сливалось в бесформенную кляксу.
Anynickname
19.07.2019 22:01+1В данном шрифте больше всего места займёт Ё (2х7, или 14 кв. пикс.), что больше, чем Щ (по-минимуму, 6 кв. пикс.).
Rim13
19.07.2019 21:47+3". Мы этого делать не будем, потому что арифметическое кодирование — сложная штука, а 68 байт — это и так немного."
Если уж начали делать минимализм(который никому не нужен), то делайте его полноценным.synedra Автор
20.07.2019 06:37Это правда. Мне тоже в статье не хватило выжимания ещё десятка байт каким-нибудь хитрым сжатием.
Rim13
20.07.2019 09:27«На самом деле файл можно сделать ещё меньше, используя кодирование палиндромов, интервальное кодирование и другие методики сжатия. Как и арифметическое кодирование, эти методики мы отложим до следующей статьи.»
А есть это статья?synedra Автор
21.07.2019 08:18Насколько мне известно, нет. В репозитории оригинала последний коммит что-то около месяца назад, так что, может быть, ещё пишется.
KvanTTT
21.07.2019 12:46Я видел пост еще в конце 2018 года: 1px-wide font. Правда эта статья намного более подробная.
resetme
19.07.2019 22:23Получается что в таком шрифте 5 позиций для кодирования. Это мне напомнило азбуку Морзе. Если записать те же буквы вертикальными столбцами в азбуке Морзе, то читать будет даже проще.
Tyusha
20.07.2019 15:41+1Вы ничего не поняли. Вопрос не в том, чтобы закодировать азбуку, а в том, чтобы создать шрифт.
sidristij
19.07.2019 23:08+7Вы сейчас открыли новые возможности для написания неудобных пунктов договоров мелким шрифтом
engine9
19.07.2019 23:43+2Элегантно! Напомнило как делали в старину дополнительные цвета.
Наглядно
JKot
20.07.2019 07:34Вот только эта техника была продемонстрирована в 2015, а не в старину.
sacred1972
21.07.2019 23:42+1Да, такое количество цветов на CGA — недавнее открытие. Но более 4-х (что превышает документированные возможности) использовалось во многих старых играх nerdlypleasures.blogspot.com/2013/11/ibm-pc-color-composite-graphics.html
Ну доступно это было, конечно только на композитных мониторах.
vanxant
20.07.2019 03:08В детстве баловался шрифтами типа 3х6 для Спектрума, и они были намного читаемее чем вот это вот.
Crevice
20.07.2019 06:56Сфотографировал телефоном с монитора. Не ожидал, что будет так отлично видно.
aik
20.07.2019 09:08То, что в статье приводится как «программно увеличенное» нечитаемо вообще, даже если знаешь, что там написано.
А вот в оригинальном размере читаемо только под лупой. Действительно разборчиво. Но под лупой, которую надо подносить к монитору.Maximuzzz
20.07.2019 15:04+9Так оно и не должно быть читаемо, по сути «программно увеличенное» изображение показывает как именно (каким цветом) закодирован символ. Каждый цветной пиксель разбивается дисплеем на 3 составляющие, в виду технических особенностей, и получается «магия». Собственно в статье об этом и рассказывается.
McAaron
20.07.2019 12:03-11) Насколько маленьким может быть читаемый шрифт?
Очень неопределнный вопрос. Сразу возникает уточняющий вопрос:
— на каком устройстве? Например, используя лазерный принтер, можно печатать на качественной пленке с разрешением 1200x600 dpi — это выдает по паспорту мой принтер и я его использовал для печати миры, чтобы проверить возможности фотосканера Epson Perfection Photo 4870 в проходящем свете. Если кодировать прямо в postscript, можно печатать отдельными пикселями вышеуказанного размера. «Шахматная доска» из пикселей остается разборчивой. То же самое получается на хорошей мелованной бумаге. И это получается на устройстве типа ширпотреб. Есть еще более «тонкое» устройство — степпер. Каждый жук и жаба, проектирующий чипы и заказывающий их на фабах, считает своим долгом что-нибудь написать мелкими буквами на кремнии.
2) Сколько памяти понадобится, чтобы его хранить?
Все зависит от представления. Для растрового представления достаточно широкого набора разборчиво читаемых символов хватает поля 5x7. Это 35 бит. Если сжать без потерь, будет где-то 16-19, в зависимоти от алгоритма сжатия. Если с потерями, но без потери разборчивости на латинке, можно догнать до 7 бит на символ. Если использовать словарь для фильтрации вариантов, можно догнать до 5.
3) Сколько кода понадобится, чтобы его использовать?
Смотря что понимать под кодом. ЕМНИП, программа рендеринга шахматного поля с клеткой в один пиксель на постскрипте (файл, который отправлялся на принтер командой lp), весил чуть более 300 байт.
Если просто печатать текст шрифтом по умолчанию, на устройство, в том числе и на экран, то на символ ASCII идет ровно один байт.
vk2
20.07.2019 12:29+7Вспоминается БК 0010-01
(на черно-белом мониторе выглядело прекрасно, разумеется)Kirhgoff
21.07.2019 03:02Всегда мечтал о цветном мониторе для моей бкшки :(
engine9
21.07.2019 10:32Такие вообще были?
Kirhgoff
21.07.2019 11:17Ну вот мужчина выше скриншот запостил. Но вообще, понятия не имею. Мечтают ведь о том, чего нет. Мне кажется я где-то увидел цветной монитор, возможно у другого компьютера и возжелал такого же. Помню только, как я мечтал разными цветами рисовать в своей программе.
vk2
21.07.2019 13:02Да, и поставлялись очень широко (я его неоднократно видел в разных компьютерных классах). Нашел фотографию такого, как у был меня. Кстати, я-то как раз хотел черно-белый, чтобы с узенькими символами работать.
firk
20.07.2019 20:53-1С 36 используемыми символами получается ровно 36х5 пикселей. Если считать, что каждый пиксель занимает 3 байта, то нам нужно 3653 = 540 байт на то, чтобы хранить весь рисуонк
Сжатие
Палитра
итдЯ конечно понимаю что в итоге автор статьи пришёл к верному решению, но количество попутно совершённых при этом заведомо лишних действий удивляет.
Всё же просто: символы состоят из 3х5 точек, каждая точка либо включена либо нет (1 бит). То есть это монохромная картинка 3х5 с 1 битом на пиксель. То что её выводят на монитор в субпиксельном режиме — для кодирования дела нисколько не меняет.
Maccimo
20.07.2019 22:01Банальные чёрные поля добавляют читабельности:
Если для компонентов пикселя не использовать исключительно 0x00 и 0xFF, а подбирать значения в зависимости от соседних точек и восприятия разных цветов человеком, то наверняка можно сделать ещё читабельнее.
afendiko
21.07.2019 08:21Проблемы со шрифтом на компе не существует. А вот, например текст на бумаге, написанный очень мелким шрифтом, реально раздражает. Иногда приходиться фотографировать его, чтобы прочитать.
seri0shka
21.07.2019 12:20+1По факту на один символ требуется 5 пикселей по вертикали, ещё один добавляется для междустрочного интервала- уже 6 пикселей, и плюс ещё столько же (!) для интервала между буквами. Итого 12 пикселей занимает один символ. Возмутимо много.
Можно для интервала между буквами использовать 1 субпиксель, это в 1.5 раза уменьшит занимаемое место.
нарисовалKvanTTT
21.07.2019 12:44А можно картинку в оригинальном масштабе? Подозреваю, что качество так будет хуже, потому что слова вообще будут сливаться.
seri0shka
21.07.2019 13:57+1Оказывается, можно даже в редакторе, вот картинка:
Вы открыли во мне новые возможности.фото с дисплеяsynedra Автор
22.07.2019 07:30Неплохо, но тогда на каждый символ нужно три глифа в зависимости от его позиции по субпикселям. Посмотрите на, например, L в слове Hello. Это либо раздует атлас втрое, либо (что логичнее) потребует хранить чёрно-белый атлас и генерировать колонки на лету.
seri0shka
22.07.2019 10:53хранить чёрно-белый атлас и генерировать колонки на лету.
Именно!Насколько маленьким может быть читаемый шрифт?
Если во главу угла поставить первый вопрос, то мы получили требуемое.
Сколько памяти понадобится, чтобы его хранить?
Сколько кода понадобится, чтобы его использовать?
KvanTTT
21.07.2019 12:47А нет ли онлайн сервиса, в котором можно сгенерировать произвольный текст таким шрифтом?
KvanTTT
21.07.2019 12:50В качестве дальнейшего развития можно попробовать генерировать субпиксельные анимации. Сделать гифку или даже игру.
JTG
Пост будет неполным без ссылки на «Властелина Колец», набранного этим шрифтом files.morr.cc/lotr1.png
VereNick
Хм, есть пруфы что это «Властелин Колец»?
synedra Автор
Первые слова текста "THE LORD OF THE RINGS PART ONE THE FELLOWSHIP OF THE RING J.R.R. TOLKIEN". Что-то мне подсказывает, что это таки не Библия короля Якова.
Anynickname
Скачал счас ВК в txt, получилось порядка 2.8 Мб. Стало быть 2.8М символов (с учетом пробелов, пунктуации и переносов строк). Там в конце куча аппендиксов, в начале содержание, номера страниц и глав, т.ч. если всё это сократить, то можно допустить, что основной объем текста будет в р-не 2+ М.
По данным интернетов, в трилогии порядка 500к слов, что косвенно подтверждает оценку в 2М или даже 2,5М символов (при средней длине слова в английском в р-не 4 символов). По крайней мере расхождение результата с фактом при такой оценке вряд ли будет в разы.
Приведённый файл, если обрезать ~20пикс. поля, имеет разрешение 3460х3460 пикселов, при размере символа (5+1)*(1+1)=12 кв. пикс. выходит чуть меньше миллиона символов, что в 2 раза меньше ожидаемой цифры.
Что не так?