Вопрос: Мне нужно обновить старое графическое математическое приложение Embarcadero VCL, добавив сглаженные линии. Итак, я написал на C++ алгоритм, указанный на странице: https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm.
Как правильно написать функцию «график» для рисования пикселя в точке (x, y) с яркостью «с», особенно на Embarcadero VCL.
Решение: Это решение стало возможным благодаря вкладу @Spektre (использование объединения для смешивания цветов в соответствии с некоторой яркостью). pC — указатель холста, funcColor — предполагаемый цвет линии и свойства класса Observer:
//Antialiased line:
void Observer::aaLine(int x0, int y0, int x1, int y1)
{
union {
uint32_t dd;//The color value
uint8_t db[4];//To work on channels: {00.RR.GG.BB}
} c, c0;//Line color, and background color
//Color mixer, with calculations on each channel, because there is no
//Alpha channel with VCL:
auto plot = [&](int X, int Y, float brightness){
c.dd = funcColor;//Line color
c0.dd = pC->Pixels[X][Y];//Background color
//Find coefficients to simulate transparency, where there is not:
//Front color is augmented when background is decreased:
for(int i = 0; i < 3; ++i)
c.db[i] = int(c.db[i] * brightness + c0.db[i] * (1 - brightness));
//Output obtained by conversion:
pC->Pixels[X][Y] = static_cast<TColor>(c.dd);
};
//Wu's algorithm:
//Fractional part of x:
auto fpart = [](double x) { return x - floor(x); };
auto rfpart = [&](double x) { return 1 - fpart(x); };
bool steep = abs(y1 - y0) > abs(x1 - x0);//Means slope > 45 deg.
if (steep) {
std::swap(x0, y0);
std::swap(x1, y1);
}
if ( x0 > x1 ) {
std::swap(x0, x1);
std::swap(y0, y1);
}
double dx = x1 - x0, dy = y1 - y0, gradient = (dx == 0. ? 1. : dy/dx) ;
//Handle first endpoint
double xend = x0,
yend = y0 + gradient * (xend - x0),
xgap = rfpart(x0 + 0.5),
xpxl1 = xend, // this will be used in the main loop
ypxl1 = floor(yend);
if ( steep ) {
plot(ypxl1, xpxl1, rfpart(yend) * xgap);
plot(ypxl1+1, xpxl1, fpart(yend) * xgap);
}
else {
plot(xpxl1, ypxl1 , rfpart(yend) * xgap);
plot(xpxl1, ypxl1+1, fpart(yend) * xgap);
}
auto intery = yend + gradient; // first y-intersection for the main loop
//Handle second endpoint
xend = round(x1);
yend = y1 + gradient * (xend - x1);
xgap = fpart(x1 + 0.5);
auto xpxl2 = xend, //this will be used in the main loop
ypxl2 = floor(yend);
if ( steep ){
plot(ypxl2 , xpxl2, rfpart(yend) * xgap);
plot(ypxl2+1, xpxl2, fpart(yend) * xgap);
//Main loop:
for(double x = xpxl1 + 1 ; x <= xpxl2 - 1 ; x += 1) {
plot(int(intery) , x, rfpart(intery));
plot(int(intery+1), x, fpart(intery));
intery += gradient;
}
}
else {
plot(xpxl2, ypxl2, rfpart(yend) * xgap);
plot(xpxl2, ypxl2+1, fpart(yend) * xgap);
//Main loop:
for(double x = xpxl1 + 1 ; x <= xpxl2 - 1 ; x += 1) {
plot(x, int(intery), rfpart(intery));
plot(x, int(intery+1), fpart(intery));
intery += gradient;
}
}
}//Observer::aaLine.
Приведенный выше исходный код обновлен и работает для меня как решение. Изображение ниже взято из тестов: синие НЕ сглажены, а красные являются результатом решения, приведенного выше. Я доволен тем, что я хочу делать.
Это скорее проблема отсутствующих пикселей при уменьшении изображений, а не сглаживание при отрисовке линии? Но в любом случае это определенно интересно, потому что мы также намерены создавать миниатюры изображений, сделанных приложением. Так что это полезно для создания миниатюр приложений.
Значения 8-битного кода RGB обычно нелинейны по отношению к яркости.
traduce, вероятно, не то слово, которое вы имели в виду.
@Вик. Ну... английский не мой родной язык, так что вы предлагаете вместо этого? Спасибо
Я не уверен, что вы имели в виду. Ты хотел сказать "уменьшить"? Похоже, вы хотите нарисовать сглаженную линию на произвольном фоне, но вы хотите, чтобы линия была определенного цвета, а не просто черного на белом или белого на черном, но вы хотели бы иметь возможность указать конкретный цвет для линии и его смешивание с фоном соответствующим образом, но образец, с которым вы работаете, не поддерживает эту функцию, и у вас есть какие-то неудовлетворительные результаты с вашим кодом. Это правильно?
Возможно, лучше было бы использовать слова «представлять», «переводить» или «выражать»? (или другой? Извините, это довольно непросто для меня, поэтому я отредактирую для этого). В остальном, что вы объясняете, я согласен: решение не идеально, я полагаю, но VCL также (намного), и этого решения для меня достаточно: это не графическое приложение, это математическое приложение. Решение быстрое, простое для понимания и ремонтопригодное. Улучшение качества приложения благодаря этому простому решению ОГРОМНОЕ, и я полностью доволен.
Пост Q&A переписан с учетом замечаний читателей.
Я думаю, что ваша проблема заключается в этом:
auto plot = [&](double X, double Y, double brighness){
pC->Pixels[X][Y] = brightness; };
Если я правильно понимаю, pC - это какая-то цель TCanvas
... у этого есть 2 основные проблемы:
pC->Pixels[X][Y] = brightness;
будет обрабатывать brightness
как цвет в соответствии с выбранным режимом (например, копирование, xor,... или что-то еще), а не как яркость.
Я бы использовал форму альфа-смешивания, когда вы берете исходный цвет рендеринга (или фона) и желаемый цвет визуализируемой линии и смешиваете его с brightness
в качестве параметра:
TColor c0=pC->Pixels[X][Y],c0=color of your line;
// here mix colors c = (c0*(1.0-brightness)) + (c1*brightness)
// however you need to do this according to selected pixelformat of you graphic object and color channel wise...
pC->Pixels[X][Y]=c;
Остерегайтесь, что прозрачность VCL не использует альфа-параметр, он просто непрозрачен или нет... Для получения дополнительной информации о микшировании см. Аналогично:
особенно обратите внимание на:
union
{
DWORD dd;
BYTE db[4];
} c,c0;
поскольку TColor в любом случае 32-битный...
скорость pC->Pixels[X][Y]
в VCL (или любом API на основе GDI) в лучшем случае жалкая
если вы обрабатываете много пикселей, вам следует рассмотреть возможность использования ScanLine[Y]
из Graphics::TBitmap
... и рендеринга в растровое изображение в качестве резервного буфера. Это обычно улучшает скорость от ~ 1000 до ~ 10000 раз. для получения дополнительной информации см.:
В вашем коде: ``` // здесь смешиваем цвета c = (c0*(1.0-brightness)) + (c1*brightness) ``` Если c0 — это запрошенный цвет линии, что такое c1? Спасибо за полный ответ выше.
@Hugo c0
- это цвет, который уже отображается под вашей линией (фон или что-то еще, если вы уже что-то там визуализировали), а c1
- желаемый цвет вашей линии ...
Здесь (neftali.clubdelphi.com/redimensionar-una-imagen-antialiasing) вы можете найти пример и объяснение сглаживания с помощью VCL (delphi). Если вы работаете с C++, вы можете адаптировать код. Это на испанском, но вы можете использовать Google Translate.