сохранение PImage в 1 битное (монохромное) BMP изображение, проблема сглаживания контуров.

krepton85
Offline
Зарегистрирован: 02.02.2016

Помогите решить проблему с выравниванием контуров в 1 битном монохромном BMP изображении. Делаю проект "Электронные ценники"(ЭЦ) на Ардуино (esp8266), XAMP (это такой веб-сервер, все в одном пакете: PHP, Apach, Mysql), processing. ЭЦ использует монохромный (без оттенков серого) дисплей на электронных чернилах. Мне нужно в Processing генерировать 1 битные BMP картинки, эту функцию я написал для Processing  сам, т.к. последний сохраняет по умолчанию только 24 битные BMP картинки. 

PImage img1;


void setup() {
  size(500, 400);
  img1 = loadImage("/data/24_bits.bmp");
  //img2 = loadImage("flower.jpg");
  //
  //img1.filter(DILATE);
  //img1.filter(ERODE);
  //img1.filter(BLUR, 0.5);  
  //img1.filter(THRESHOLD, 0.5);
  
  img1.filter(THRESHOLD, 0.7);
  sav_BMP_1bit("/data/1_bits.bmp", img1);
}

void draw() {
  image(img1, 0, 0);
  //image(img2, width*2, 0);
}

void sav_BMP_1bit(String sav_path, PImage img){
//описание формата BMP: http://www.dragonwins.com/domains/getteched/bmp/bmpfileformat.htm
println("начинаю строить 1 битное изображение BMP...");
println("Заголовок файла...");
//====заголовок файла============================
 byte[] buf = {0x42,0x4d};//BM
 int w_img = img.width;
 int h_img = img.height;
 print("Ширина изображения: "); println(w_img);
 print("Высота изображения: "); println(h_img);
 
 int zero_bytes = 0;//количество нулевых байт вконце строки, что бы количество байт одной строки было кратно 4

 
 if( (w_img / 8) % 4 != 0  && (w_img % 8 == 0) ){//если не кратно 4 количество байт одной строки и кратно 8
  for(int c = 0; c < 10; c++){
    if( ((w_img / 8) + c) % 4 == 0){//найдено большее кратное 4 число
      zero_bytes = c;
      break;
    }
  }
   
 }
 
 if( (w_img / 8) % 4 != 0  && (w_img % 8 != 0) ){//если не кратно 4 количество байт одной строки и не кратно 8
  for(int c = 0; c < 10; c++){
    if( ((w_img / 8) + c) % 4 == 0){//найдено большее кратное 4 число
      zero_bytes = c-1;
      break;
    }
  }
   
 }
 
 
 
 print("zero_bytes: "); println(zero_bytes);
 
 //zero_bytes--;
 int biSizeImage = (w_img * h_img) / 8 + (zero_bytes * h_img);
 print("Размер изображения в байтах: "); println(biSizeImage);
 //append(test,byte2(0));
 int bfSize = 14+40+ 8+((w_img * h_img)/8) + (zero_bytes * h_img);//?
 print("Размер файла в байтах: "); println(bfSize);
 byte [] size = new byte[4];

 size[0] = byte(bfSize & 0xFF);
 size[1] = byte((bfSize >> 8) & 0xFF);
 size[2] = byte((bfSize >> 16) & 0xFF);
 size[3] = byte((bfSize >> 24) & 0xFF);
 
 buf = append(buf, byte(size[0]));//bfSize
 buf = append(buf, byte(size[1]));//bfSize
 buf = append(buf, byte(size[2]));//bfSize
 buf = append(buf, byte(size[3]));//bfSize
 
 buf = append(buf, byte(0x00));//bfReserved1
 buf = append(buf, byte(0x00));//bfReserved1
 buf = append(buf, byte(0x00));//bfReserved2
 buf = append(buf, byte(0x00));//bfReserved2
 
 buf = append(buf, byte(0x3e));//bfOffBits 62 байта
 buf = append(buf, byte(0x00));//bfOffBits
 buf = append(buf, byte(0x00));//bfOffBits
 buf = append(buf, byte(0x00));//bfOffBits
 
 println("Заголовок изображения...");
 //================= заголовок изображения ============================
 buf = append(buf, byte(0x28));//biSize 40byte
 buf = append(buf, byte(0x00));//biSize
 buf = append(buf, byte(0x00));//biSize
 buf = append(buf, byte(0x00));//biSize
 
 
 buf = append(buf, byte(w_img & 0xFF));//biWidth
 buf = append(buf, byte( (w_img >> 8) & 0xFF) );//biWidth
 buf = append(buf, byte( (w_img >> 16) & 0xFF) );//biWidth
 buf = append(buf, byte( (w_img >> 24) & 0xFF) );//biWidth
 
 buf = append(buf, byte(h_img & 0xFF) );//biHeight
 buf = append(buf, byte( (h_img >> 8) & 0xFF) );//biHeight
 buf = append(buf, byte( (h_img >> 16) & 0xFF) );//biHeight
 buf = append(buf, byte( (h_img>>24)& 0xFF) );//biHeight
 
 buf = append(buf, byte(0x01));//biPlanes
 buf = append(buf, byte(0x00));//biPlanes
 
 buf = append(buf, byte(0x01));//biBitCount
 buf = append(buf, byte(0x00));//biBitCount
 
 buf = append(buf, byte(0x00));//biCompression
 buf = append(buf, byte(0x00));//biCompression
 buf = append(buf, byte(0x00));//biCompression
 buf = append(buf, byte(0x00));//biCompression
 
 buf = append(buf, byte( biSizeImage & 0xFF) );//biSizeImage
 buf = append(buf, byte( (biSizeImage >> 8) & 0xFF));//biSizeImage
 buf = append(buf, byte((biSizeImage >> 16) & 0xFF));//biSizeImage
 buf = append(buf, byte((biSizeImage >> 24) & 0xFF));//biSizeImage
 
 buf = append(buf, byte(0x00));//biXPelsPerMeter
 buf = append(buf, byte(0x00));//biXPelsPerMeter
 buf = append(buf, byte(0x00));//biXPelsPerMeter
 buf = append(buf, byte(0x00));//biXPelsPerMeter
 
 buf = append(buf, byte(0x00));//biYPelsPerMeter
 buf = append(buf, byte(0x00));//biYPelsPerMeter
 buf = append(buf, byte(0x00));//biYPelsPerMeter
 buf = append(buf, byte(0x00));//biYPelsPerMeter
 
 buf = append(buf, byte(0x02));//biClrUsed
 buf = append(buf, byte(0x00));//biClrUsed
 buf = append(buf, byte(0x00));//biClrUsed
 buf = append(buf, byte(0x00));//biClrUsed
 
 buf = append(buf, byte(0x02));//biClrImportant
 buf = append(buf, byte(0x00));//biClrImportant
 buf = append(buf, byte(0x00));//biClrImportant
 buf = append(buf, byte(0x00));//biClrImportant

 println("Таблица цветов...");
 //===================== Таблица цветов =================================
 buf = append(buf, byte(0x00));//black
 buf = append(buf, byte(0x00));//black
 buf = append(buf, byte(0x00));//black
 buf = append(buf, byte(0x00));//black
 
 buf = append(buf, byte(0xFF));//white
 buf = append(buf, byte(0xFF));//white
 buf = append(buf, byte(0xFF));//white
 buf = append(buf, byte(0x00));//white

 
 println("Пиксельные данные...");
 //=====================пиксельные данные================================
 color c;
 int data = 0;
 int bits_offset = 7;
 int counts_bytes = 0;
 //PImage test_img = loadImage("G:/Programs/XAMPP/htdocs/public_html/img/red_nambers.bmp");
 
 //int pixel = 0;
 //test_img.loadPixels();
 for(int rows = h_img; rows > 0; rows--){//ряды пикселей
   
   for(int columns = 0; columns < w_img; columns++){//столбцы пикселей
    c = img.get(columns, rows);
    //test_img.pixels[pixel] = c;
    
  // c = img.pixels[pixel];
    //pixel++;
    
    
     if((c >> 16 & 0xFF) < 180 && (c >> 8 & 0xFF) < 180 && (c & 0xFF) < 180){
     //print(".");  
     data |= (0 << bits_offset);//black pixel
     
     }
    /* 
    else if(c == white){
     //print(" ");
     data |= (1 << bits_offset);//white pixel
     }
     */
    else if( (c >> 16 & 0xFF) > 180 && (c >> 8 & 0xFF) > 180 && (c & 0xFF) > 180) {//default color
     data |= (1 << bits_offset);//white pixel 
      
    }else{
      //print("Новый цвет: R:");print( (c>>16) & 0xFF); print(", G:");print( (c>>8) & 0xFF);print(", B:");println( c & 0xFF);
      //data |= (1 << bits_offset);//white pixel
      //data |= (0 << bits_offset);//black pixel
    }
              
     
              
              
   if(bits_offset == 0 || columns == (w_img - 1) ){
       bits_offset = 7;
    
       buf = append(buf, byte(data));
       counts_bytes++;       
       //print(binary(byte(data) ));
       data = 0;
     }else if(bits_offset > 0){
       bits_offset--;
     }    
     
   }
   
   for(int z = 0; z < zero_bytes; z++){
        buf = append(buf, byte(0x00));//дабовление нулевых байт для кратности 4
        counts_bytes = counts_bytes + 1;
       }
 // println();
   
   
   
 }
 print("counts_bytes: "); println(counts_bytes);
 //biSizeImage = counts_bytes;
 
 
 //buf[0x22] = byte( biSizeImage & 0xFF);
 //buf[0x23] = byte( (biSizeImage >> 8) & 0xFF);
 //buf[0x24] = byte( (biSizeImage >> 16) & 0xFF);
 //buf[0x25] = byte( (biSizeImage >> 24) & 0xFF);
 
 println("Сохраняю...");
 saveBytes(sav_path ,buf); //
 //test_img.updatePixels();
 //test_img.save(path_folder_images + "/" + "def2" + ".bmp");
 
 print("1 битное изображение BMP сохранено в: "); println(sav_path);
   
}

Проблема с текстом в картинках 1 бит, контуры становятся кривыми (особенно заметно на диагональных линиях, а особо тонкие в шрифтах и вовсе исчезают), т.к. оказалось что в 24 битных BMP для выравнивания контуров используются пикселя с оттенками серого (если текст черный) и при построении 1 битного BMP эти пикселя серые теряются (их конечно можно группировать в черный или белый пиксель, но все равно не то) . Кстати Paint точно так же сохраняет в 1 битное изображение. Исходник и пример BMP ЭЦ в папке "data" на Яндекс Диске:

https://yadi.sk/d/qBrnQodGExmhHg

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

А хочется-то чего?

krepton85
Offline
Зарегистрирован: 02.02.2016

Хочется что бы линии в шрифтах были ровнее, т.е. менее заметней "ступеньки". В общем что бы 1 битное BMP выглядело как 24 битное, монохромное. Какой то алгоритм нужен который будет дорисовывать черные пиксели к черному тексту. Да и вообще как то же делают шрифты C++ для Ардуино из привычных шрифтов ttf, otf и выглядят они ровно на экранах наших. Вообще ни когда не думал что шрифты ttf, otf, могут содержать оттенки серого для сглаживания по контуру символа.

rkit
Offline
Зарегистрирован: 23.11.2016

krepton85 пишет:

Какой то алгоритм нужен который будет дорисовывать черные пиксели к черному тексту

И будет просто новая ступенька.

krepton85
Offline
Зарегистрирован: 02.02.2016

они станут мельче, толщиной хотя бы в 1 пиксель, а не 5 - 10 пикселей.

rkit
Offline
Зарегистрирован: 23.11.2016

А зачем ты рендеришь шрифты с разрешением меньше разрешения экрана?

krepton85
Offline
Зарегистрирован: 02.02.2016

Нет, шрифт загружается из ttf или otf нужного размера с помощью функции:

f_name = createFont(path_font, size);

Вот окно настроек BMP цеников: https://yadi.sk/i/QXx3weT1RIAcAQ

 

rkit
Offline
Зарегистрирован: 23.11.2016

Если у тебя лесенки больше одного пикселя, то это неправильное разрешение.

krepton85
Offline
Зарегистрирован: 02.02.2016

Дело не в размере шрифта, проведите эксперемент в Paint, нарисуйте текст встроенными средствами Paint и сохраните картинку в 2-х вариантах: Монохромное 1 битное BMP изо и 24-х битное BMP. И посмотрите на разницу.

rkit
Offline
Зарегистрирован: 23.11.2016

Конечно не в размере дело. Дело в разрешении. Если ты говоришь правду, и тебя ступеньки в 10 пикселей.

Что такое антиальясинг я прекрасно знаю. И субпиксельный хинтинг тоже. И что ни то ни то не может работать на однобитной глубине цвета.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Зато на однобитном экране может работать дизеринг.

Правда, я не уверен, что ТС нужно именно это (я вообще не понял, что ТС хочет. Разумеется, кроме абсурдного "что бы 1 битное BMP выглядело как 24 битное" - если бы такое было возможно, никто не стал бы делать 24-битное).

krepton85
Offline
Зарегистрирован: 02.02.2016

Да, видимо вы правы, функция : createFont(path_font, size);
Использует самое низкое разрешение шрифта, а аргумент size просто растягивает его до нужного размера.
Это ужасно, есть в Processing и другой способ использования шрифтов ttf, otf, конвертация установленных в систему шрифтов в другой формат файла, но что то мне подсказывает, что там будет все тоже самое.
https://processing.org/reference/loadFont_.html

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018
krepton85
Offline
Зарегистрирован: 02.02.2016

Почему мой установленный в систему шрифт не доступен в меню: "Инструменты" -> "Создать шрифты..."?

https://yadi.sk/i/ARhgjUrMtZRiKA

Например шрифт "Crystal Normal" установился и доступен во всех программах Windows, но в processing он не виден, только с помощью  функции "PFont.list()" он есть в списках.

PFont test_f;
color red = color(255,0,0);

void setup(){
size(400, 400);
String[] fontList = PFont.list();
printArray(fontList);

test_f = createFont("Crystal Normal", 48);
fill(red);//цвет текста
textFont(test_f);
}

public void draw(){
  background(230);
  text("Сказка", 50, 200);
  
  
}

Но при попытке его выбрать получаю следующую ошибку: https://yadi.sk/i/Pwfv3uCdiJi_nQ

С другими же шрифтами из списка "PFont.list()" все нормально, только с моими шрифтами такая проблема.