Search
Write a publication
Pull to refresh

Comments 26

0.2126 * red + 0.7152 * green + 0.0722 * blue , и то, что дает показатель больший 128, я определяю как белый, а меньший - черный.


Есть более продвинутый метод переноса остатка (не помню имя метода, курсач делал в 1995 году).

Допустим текущий пиксел дает значение v=0.2126 * red + 0.7152 * green + 0.0722 * blue в диапазоне от 0 до 255

При превышении порога в 128 вы не только выводите белый пиксел на этом месте, но также вычисляете остаток d=v-128, который добавляете к соответсвующим v пикселей снизу, справа и справа-снизу по примерной формуле:
- 0.4 * d добавить к пикселу справа
- 0.4 * d добавить к пикселу снизу
- 0.2 * d добавить к пикселу справа-снизу (по диагонали)

Такой метод сохраняет больше информации (при превышении избыток не выкидывается) и дает более привлекательные изображения, в частности для областей где преобладает значение (для примера) 121, вы вместо пустой черноты выведете некоторую шумовую диффузность, которая издалека будет выглядеть как серый.

UPD. Внизу написали про дизеринг Флойда-Стейнберга, на Хабре и статья есть https://habr.com/ru/articles/326936/

о да, я думал над этим

спасибо за замечание, ибо я хотел написать это внутри самой статьи и забыл)

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

ну и я хотел показать тут работу с многопоточностью и в целом с пикселями, шумы - это уже мелочи

преобразование по пороговому значению весьма часто дают мерзкую картину - лучше делать дизеринг Флойда-Стейнберга.

я не хотел уходить в такую сложную реализацию

я не хотел уходить в такую сложную реализацию

void openBMP(const std::string &fileName) {

Лучше использовать std::string_view вместо const- ссылки на строку.

string_view не даёт гарантий нультерминированнлсти, а значит где-то в глубинах он будет копироваться, ибо API всех ОС ждут C-строку. Так что выгода вряд ли будет.

Нультерминированность какое имеет отношение к лишним копированиям?

Символы из переданного string_view будут внутри всё-равно копироваться в string, чтобы в какой-нибудь fopen или CreateFile передать нультерминированную строку. Учтите, я не говорю об общих случаях, когда нет необходимости модифицировать строку или передавать дальше в стороннее C-API - тогда лучше string_view по значению. Но в этом конкретном случае - выгоды нет.

Символы из переданного string_view будут внутри всё-равно копироваться в string, чтобы в какой-нибудь fopen или CreateFile передать нультерминированную строку.

Где про это прочитать можно?

Буквально недавно писал процедуру, которая записывает данные в бинарный файл. Примерно так:

std::error_code writeFile(const std::string_view filePath, uint8_t* data, size_t sz) {
		std::ofstream outputFile(filePath.data(), std::ios::binary);
...

Под Windows в MSVS (компилятор clang) можно в конструктор std::ofstream передать прямо string_view (видимо, в реализации есть оператор, который приводит string_view к const char*), а под Ubuntu компилятор gcc 13 отказывается такое компилировать, приходится явно передавать data(), а эта функция выдает просто указатель const char* - хотите сказать, здесь тоже какое-то копирование происходит?

Не помню где - вроде бы, в лекции Полухина из Яндекс про C++17 как раз говорилось про то, что со string_view при передаче в какие-нибудь функции вроде WinAPI нужно быть осторожным, т.к. string_view не обязан содержать нулевой символ в конце. Как раз потому, что никакого копирования при этом не происходит.

Если я не прав, скажите, где про это подробнее почитать можно.

Попробовал написать такой код в приложении на MFC под Windows:

void test(std::string_view name) {
	FILE* f;
	fopen_s(&f, name.data(), "w");
	if (f) fclose(f);
	auto h = CreateFileA(name.data(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
	if (h != INVALID_HANDLE_VALUE)
		CloseHandle(h);
}

Напрямую что в fopen, что в CreateFile передать string_view компилятор не дает, но можно передать указать через вызов data(). Всё собирается и работает, под отладчиком вроде не увидел, что какие-то копирования производятся - только получение указателя при вызове data().

Вызывается, например, так:

test("d:\\tmp\\file.txt");

Внутри функции test вызов name.data() выдает указатель на нуль-терминированную строку. Что я делаю не так?

Вам могут в string_view передать например часть строки, в конце не будет ноля, и всё поломается

Это понятно. Я говорю про ситуации, когда я уверен в том, что нуль в конце есть. В таком случае не нужно никакого лишнего копирования.

"Все говорят, нельзя баловаться спичками, а я баловался, и ничего не сгорело. Что я делаю не так?"
Вы уверены со стороны вызывающего функцию. А когда вы пишите функцию, надо быть уверенным со стороны вызываемого. То есть если вы параметром принимаете string_view, который не даёт гарантии, что за его концом есть 0, а нужно вам передать в C-API, которое требует нуль-терминированную строку, у вас нет никакого другого варианта, кроме как скопировать символы из string_view в string, и вызвать c_str(). До С++11 data() и в std::string не дает гарантий нуль-терминированности.
Сегодня вы так напишите, а завтра вашему коллеге дадут задачу "записать мета-инфу о файле в файл с тем же именем, но без расширения", и он напишет
write_file({file.name.c_str(), file.name.find_last_of('.')}, .....);
И затрет нахрен исходный файл. И будет прав, потому что ваша функция принимает string_view, и он ей посылает string_view, "как договаривались".
И да, проверять string_view, что за его концом лежит 0 - UB, как чтение за пределами массива.

Из GCC13

      /**
       *  @brief  Create an output file stream.
       *  @param  __s  Null terminated string specifying the filename.
       *  @param  __mode  Open file in specified mode (see std::ios_base).
       *
       *  @c ios_base::out is automatically included in @a __mode.
       */
      explicit
      basic_ofstream(const char* __s,
		     ios_base::openmode __mode = ios_base::out)

Как видите, параметром он требует Null terminated string specifying the filename.
string_view::data не дает гарантий Null terminated.
https://en.cppreference.com/w/cpp/string/basic_string_view/data

Unlike std::basic_string::data() and string literals, std::basic_string_view::data() returns
a pointer to a buffer that is not necessarily null-terminated, for example a substring
view (e.g. from remove_suffix). Therefore, it is typically a mistake to pass data() to
a routine that takes just a const CharT* and expects a null-terminated string.

Так что если уж на то пошло, то лучше передавать filesystem::path.

 вашему коллеге дадут задачу "записать мета-инфу о файле в файл с тем же именем, но без расширения", и он напишетwrite_file({file.name.c_str(), file.name.find_last_of('.')}, .....);И затрет нахрен исходный файл. И будет прав

Коллеге не подвезли std::filesystem, где есть явные функции has_extension() и replace_extension()? Прав ли он, что ими не пользуется?

Так-то можно много способов найти, как выстрелить себе в ногу. Например, не проверять указатели на nullptr. Но иногда эти проверки убирают для максимальной производительности, договариваясь о том, что внутрь определенных функций передавать nullptr нельзя.

Какой-то детский лепет и сваливание вины на других, как детский сад, ей богу.
write_file - ваша функция. И вы несете ответственность за то, что она будет правильно работать. Вам уже с cppreference привели цитату, что передавать string_view::data в функции, ждущие const CharT* и нуль-терминированную строку - это ошибка, показали случаи, когда это может вызвать проблемы - а Вы всё еще пытаетесь что-то выдавить. Вместо того, чтобы пойти и исправить ошибку в своей функции.
Аргументы функции - это контракт, который вы обязуетесь выполнять. Если вы принимаете string_view, то у пользователя вашей функции вот совсем не должна болеть голова о том, будет ли его string_view нуль-терминированным или нет.
Принимайте string и не парьтесь. Ну или раз вам подвезли std::filesystem, то filesystem::path.
А чтобы не передавали null, надо не "договариваться", а аргументом делать не указатель, а ссылку

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

А чтобы не передавали null, надо не "договариваться", а аргументом делать не указатель, а ссылку

Расскажете, как передать ссылку в интерфейс COM-объекта?

"А мы продаём или покупаем"?
Вы вызываете чужой COM-интерфейс или реализуете свой?
Вообще ничего не мешает в заголовочнике COM-интерфейса прописать в сигнатуре функции ссылку вместо указателя, работать будет. Зато всем нормальным пользователям будет понятно, что функция не ждёт NULL.
Вообще, разговор шёл про С++ интерфейсы, которые вы пишете к своему функционалу, а COM, хоть и может вызываться из C++, но всё же скорее некий C-интерфейс.
PS: вы не в 1С случайно работаете? А то в 8ке там всё внутри на COM-интерфейсах...

А мы не знаем, какое в точности было задание, про это уже ниже задали вопрос. Одно дело - написать публичную библиотеку, которую неизвестно кто вызывает и передаёт туда неизвестно откуда взявшуюся строку. Тогда да - надо принимать строку по const-ссылке.

Другое дело, если это функция строго внутри своей программы, и есть определённость по поводу того, откуда строка берётся, и есть ли в ней нулевой символ в конце. Тогда возможен вариант с передачей строк через string_view. Если бояться даже теоретически возможной ситуации, что в string_view будет строка, не оканчивающаяся нулём, так лучше вообще string_view не использовать.

Я работаю не в 1C, но так получилось, что приходится и с COM-объектами иметь дело. BSTR, кстати, тоже не обязана ноль в конце содержать. Вообще-то, есть определённые соглашения по поводу того, что можно передавать в COM-объекты, а что нельзя. В outproc-объект тоже сможете из другого EXE-шника ссылку передать?

больше спасибо вам за объяснение, я руководствовался тем же

Тестовое действительно интересное. Если не запрещено, можете конкретный его текст привести? И на какую позицию претендовали, хотя бы что там требовалось помимо общих C++, ООП и многопоточности?

Просто не очень понятно: вот на гитхабе пишете про черно-белые изображения, тогда как они могут быть бинарными (без полутонов) или монохромными (с полутонами).

Еще несколько смутила формулировка про "вывести изображение", если действительно в окне эмулятора терминала, то у вас не очень удачное решение, на мой взгляд, - чтобы рассмотреть картинку придется менять настройки шрифта. Или просто надо было открыть изображение, конвертировать и сохранить, а не выводить на экран?

Мне тоже это задание показалось интересным и я его сделал. Особо не мудрил т.к. есть опыт пары тестовых на которые не ответили. Кстати я запросил обратную связь у HR и он переслал комментарий от проверяющего.

В задании необходимо написать консольное приложение, для чтения и отображения BMP файлов. В приложении должен быть класс который читает формат BMP (24 или 32 бита) и выводит в консоль двумя разными символами два цвета: черный и белый. На вход должны подаваться только картинки содержащие 2 эти цвета. Класс должен инкапсулировать все необходимые данные по открытию и отображению картинки, а также 3 главные функции приложения: openBMP(const string & fileName), displayBMP(), closeBMP(). Для чтения из файла используется библиотека <fstream>.

С чего я балдею, так это с того, что мне 2 дня назад прислали это задание...

Sign up to leave a comment.

Articles