7#include "terrainrenderer.h"
9#include "kstars_debug.h"
12#include "skyqpainter.h"
13#include "projections/projector.h"
14#include "projections/equirectangularprojector.h"
21TerrainRenderer * TerrainRenderer::_terrainRenderer =
nullptr;
22TerrainRenderer *TerrainRenderer::Instance()
24 if (_terrainRenderer ==
nullptr)
25 _terrainRenderer =
new TerrainRenderer();
27 return _terrainRenderer;
34 TerrainLookup(
int width,
int height) :
35 valPtr(new float[width * height]), valWidth(width)
37 memset(valPtr, 0, width * height *
sizeof(
float));
43 inline float get(
int w,
int h)
45 return valPtr[h * valWidth + w];
47 inline void set(
int w,
int h,
float val)
49 valPtr[h * valWidth + w] = val;
67 InterpArray(
int width,
int height,
int samplingFactor) : sampling(samplingFactor)
69 int downsampledWidth = width / sampling;
70 if (width % sampling != 0)
71 downsampledWidth += 1;
72 lastDownsampledCol = downsampledWidth - 1;
74 int downsampledHeight = height / sampling;
75 if (height % sampling != 0)
76 downsampledHeight += 1;
77 lastDownsampledRow = downsampledHeight - 1;
79 azLookup =
new TerrainLookup(downsampledWidth, downsampledHeight);
80 altLookup =
new TerrainLookup(downsampledWidth, downsampledHeight);
91 inline void get(
int x,
int y,
float *az,
float *alt)
93 const bool rowSampled = y % sampling == 0;
94 const bool colSampled = x % sampling == 0;
95 const int xSampled = x / sampling;
96 const int ySampled = y / sampling;
99 if (rowSampled && colSampled)
101 *az = azLookup->get(xSampled, ySampled);
102 *alt = altLookup->get(xSampled, ySampled);
109 if (xSampled >= lastDownsampledCol)
113 *az = azLookup->get(xSampled, ySampled);
114 *alt = altLookup->get(xSampled, ySampled);
118 const float weight2 =
static_cast<float>(x) / sampling - xSampled;
119 const float weight1 = 1 - weight2;
120 *az = weight1 * azLookup->get(xSampled, ySampled) + weight2 * azLookup->get(xSampled + 1, ySampled);
121 *alt = weight1 * altLookup->get(xSampled, ySampled) + weight2 * altLookup->get(xSampled + 1, ySampled);
128 if (ySampled >= lastDownsampledRow)
132 *az = azLookup->get(xSampled, ySampled);
133 *alt = altLookup->get(xSampled, ySampled);
137 const float weight2 =
static_cast<float>(y) / sampling - ySampled;
138 const float weight1 = 1 - weight2;
139 *az = weight1 * azLookup->get(xSampled, ySampled) + weight2 * azLookup->get(xSampled, ySampled + 1);
140 *alt = weight1 * altLookup->get(xSampled, ySampled) + weight2 * altLookup->get(xSampled, ySampled + 1);
147 if (xSampled >= lastDownsampledCol || ySampled >= lastDownsampledRow)
150 *az = azLookup->get(xSampled, ySampled);
151 *alt = altLookup->get(xSampled, ySampled);
155 const float weight2 =
static_cast<float>(x) / sampling - xSampled;
156 const float weight1 = 1 - weight2;
157 const float azWval = (weight1 * ( azLookup->get(xSampled, ySampled) + azLookup->get(xSampled, ySampled + 1)) +
158 weight2 * ( azLookup->get(xSampled + 1, ySampled) + azLookup->get(xSampled + 1, ySampled + 1)));
159 const float altWval = (weight1 * (altLookup->get(xSampled, ySampled) + altLookup->get(xSampled, ySampled + 1)) +
160 weight2 * (altLookup->get(xSampled + 1, ySampled) + altLookup->get(xSampled + 1, ySampled + 1)));
163 const float hweight2 =
static_cast<float>(y) / sampling - ySampled;
164 const float hweight1 = 1 - hweight2;
165 const float azHval = (hweight2 * ( azLookup->get(xSampled, ySampled) + azLookup->get(xSampled + 1, ySampled)) +
166 hweight1 * ( azLookup->get(xSampled, ySampled + 1) + azLookup->get(xSampled + 1, ySampled + 1)));
167 const float altHval = (hweight2 * (altLookup->get(xSampled, ySampled) + altLookup->get(xSampled + 1, ySampled)) +
168 hweight1 * (altLookup->get(xSampled, ySampled + 1) + altLookup->get(xSampled + 1, ySampled + 1)));
170 *az = (azWval + azHval) / 4.0;
171 *alt = (altWval + altHval) / 4.0;
175 TerrainLookup *azimuthLookup()
179 TerrainLookup *altitudeLookup()
187 int lastDownsampledCol = 0;
188 int lastDownsampledRow = 0;
192 TerrainLookup *azLookup =
nullptr;
193 TerrainLookup *altLookup =
nullptr;
196TerrainRenderer::TerrainRenderer()
201double rationalizeAz(
double degrees)
203 if (degrees < -1000 || degrees > 1000)
208 while (degrees >= 360.0)
214double rationalizeAlt(
double degrees)
226QRgb TerrainRenderer::getPixel(
double az,
double alt)
const
228 az = rationalizeAz(az + Options::terrainSourceCorrectAz());
231 alt = alt - Options::terrainSourceCorrectAlt();
232 if (az < 0 || az >= 360 || alt < -90 || alt > 90)
238 const int width = sourceImage.
width();
239 const int height = sourceImage.
height();
241 if (!Options::terrainSmoothPixels())
244 int pixX = width / 2 + (az / 360.0) * width;
245 if (pixX > width - 1)
249 int pixY = ((alt + 90.0) / 180.0) * height;
250 if (pixY > height - 1)
252 pixY = (height - 1) - pixY;
253 return sourceImage.
pixel(pixX, pixY);
257 float pixX = width / 2 + (az / 360.0) * width;
258 if (pixX > width - 1)
262 float pixY = ((alt + 90.0) / 180.0) * height;
263 if (pixY > height - 1)
265 pixY = (height - 1) - pixY;
268 constexpr int lowAlpha = 0.1 * 255;
269 if (qAlpha(sourceImage.
pixel(pixX, pixY)) < lowAlpha)
270 return sourceImage.
pixel(pixX, pixY);
274 int x1 =
static_cast<int>(pixX);
275 int y1 =
static_cast<int>(pixY);
277 if ((x1 >= width - 1) || (y1 >= height - 1))
278 return sourceImage.
pixel(x1, y1);
281 float wx2 = pixX - x1;
282 float wx1 = 1.0 - wx2;
283 float wy2 = pixY - y1;
284 float wy1 = 1.0 - wy2;
287 QRgb c11(qUnpremultiply(sourceImage.
pixel(pixX, pixY)));
288 QRgb c12(qUnpremultiply(sourceImage.
pixel(pixX, pixY + 1)));
289 QRgb c21(qUnpremultiply(sourceImage.
pixel(pixX + 1, pixY)));
290 QRgb c22(qUnpremultiply(sourceImage.
pixel(pixX + 1, pixY + 1)));
293 float w11 = wx1 * wy1;
294 float w12 = wx1 * wy2;
295 float w21 = wx2 * wy1;
296 float w22 = wx2 * wy2;
299 int red = w11 * qRed(c11) + w12 * qRed(c12) + w21 * qRed(c21) + w22 * qRed(c22);
300 int green = w11 * qGreen(c11) + w12 * qGreen(c12) + w21 * qGreen(c21) + w22 * qGreen(c22);
301 int blue = w11 * qBlue(c11) + w12 * qBlue(c12) + w21 * qBlue(c21) + w22 * qBlue(c22);
302 int alpha = w11 * qAlpha(c11) + w12 * qAlpha(c12) + w21 * qAlpha(c21) + w22 * qAlpha(c22);
303 return qPremultiply(qRgba(red, green, blue, alpha));
311bool TerrainRenderer::sameView(
const Projector *proj,
bool forceRefresh)
315 const auto &lst = KStarsData::Instance()->
lst();
316 const auto &lat = KStarsData::Instance()->
geo()->
lat();
322 const double az = rationalizeAz(point.
az().
Degrees());
323 const double alt = rationalizeAlt(point.
alt().
Degrees());
325 bool ok = view.width == savedViewParams.width &&
326 view.height == savedViewParams.height &&
327 view.zoomFactor == savedViewParams.zoomFactor &&
328 view.rotationAngle == savedViewParams.rotationAngle &&
329 view.useRefraction == savedViewParams.useRefraction &&
330 view.useAltAz == savedViewParams.useAltAz &&
332 const double azDiff = fabs(savedAz - az);
333 const double altDiff = fabs(savedAlt - alt);
334 if (!forceRefresh && ok && azDiff < .0001 && altDiff < .0001)
338 savedViewParams = view;
339 savedViewParams.focus =
nullptr;
345bool TerrainRenderer::render(uint16_t w, uint16_t h,
QImage *terrainImage,
const Projector *proj)
351 QString filename = Options::terrainSource();
352 if (!initialized || filename != sourceFilename)
356 if (image.
load(filename))
360 sourceFilename = filename;
371 Options::setShowTerrain(
false);
380 if ((terrainDownsampling != Options::terrainDownsampling()) ||
381 (terrainSmoothPixels != Options::terrainSmoothPixels()) ||
382 (terrainSkipSpeedup != Options::terrainSkipSpeedup()) ||
383 (terrainTransparencySpeedup != Options::terrainTransparencySpeedup()) ||
384 (terrainSourceCorrectAz != Options::terrainSourceCorrectAz()) ||
385 (terrainSourceCorrectAlt != Options::terrainSourceCorrectAlt()))
388 terrainDownsampling = Options::terrainDownsampling();
389 terrainSmoothPixels = Options::terrainSmoothPixels();
390 terrainSkipSpeedup = Options::terrainSkipSpeedup();
391 terrainTransparencySpeedup = Options::terrainTransparencySpeedup();
392 terrainSourceCorrectAz = Options::terrainSourceCorrectAz();
393 terrainSourceCorrectAlt = Options::terrainSourceCorrectAlt();
395 if (sameView(proj, dirty))
398 *terrainImage = savedImage.
copy();
408 const int sampling = Options::terrainDownsampling();
409 InterpArray interp(w, h, sampling);
412 setupLookup(w, h, sampling, proj, interp.azimuthLookup(), interp.altitudeLookup());
414 const double setupTime = setupTimer.
elapsed() / 1000.0;
417 const bool skip = Options::terrainSkipSpeedup() || SkyMap::IsSlewing();
418 int increment = skip ? 2 : 1;
421 terrainImage->
fill(0);
425 bool lastTransparent =
false;
426 for (
int j = 0; j < h; j += increment)
428 bool notLastRow = j != h - 1;
429 for (
int i = 0; i < w; i += increment)
431 if (lastTransparent && Options::terrainTransparencySpeedup())
435 lastTransparent =
false;
440 bool equiRectangular = (proj->
type() == Projector::Equirectangular);
446 interp.get(i, j, &az, &alt);
447 const QRgb pixel = getPixel(az, alt);
448 terrainImage->
setPixel(i, j, pixel);
449 lastTransparent = (pixel == 0);
454 bool notLastCol = i != w - 1;
456 terrainImage->
setPixel(i + 1, j, pixel);
458 terrainImage->
setPixel(i, j + 1, pixel);
459 if (notLastRow && notLastCol)
460 terrainImage->
setPixel(i + 1, j + 1, pixel);
468 savedImage = terrainImage->
copy();
470 QFile f(sourceFilename);
472 QString fName(fileInfo.fileName());
473 QString dbgMsg(
QString(
"Terrain rendering: %1px, %2s (%3s) %4 ds %5 skip %6 trnsp %7 pan %8 smooth %9")
475 .arg(timer.elapsed() / 1000.0, 5,
'f', 3)
476 .
arg(setupTime, 5,
'f', 3)
478 .
arg(Options::terrainDownsampling())
479 .
arg(Options::terrainSkipSpeedup() ?
"T" :
"F")
480 .
arg(Options::terrainTransparencySpeedup() ?
"T" :
"F")
481 .
arg(Options::terrainPanning() ?
"T" :
"F")
482 .
arg(Options::terrainSmoothPixels() ?
"T" :
"F"));
493void TerrainRenderer::setupLookup(uint16_t w, uint16_t h,
int sampling,
const Projector *proj, TerrainLookup *azLookup,
494 TerrainLookup *altLookup)
496 const auto &lst = KStarsData::Instance()->
lst();
497 const auto &lat = KStarsData::Instance()->
geo()->
lat();
498 for (
int j = 0, js = 0; j < h; j += sampling, js++)
500 for (
int i = 0, is = 0; i < w; i += sampling, is++)
503 bool equiRectangular = (proj->
type() == Projector::Equirectangular);
510 : proj->fromScreen(imgPoint, lst, lat, true);
511 const double az = rationalizeAz(point.
az().
Degrees());
512 const double alt = rationalizeAlt(point.
alt().
Degrees());
513 azLookup->set(is, js, az);
514 altLookup->set(is, js, alt);
Implememntation of Equirectangular projection
const CachingDms * lat() const
static KStars * Instance()
void syncOps()
Sync Options to GUI, if any.
The Projector class is the primary class that serves as an interface to handle projections.
virtual bool unusablePoint(const QPointF &p) const
Check if the current point on screen is a valid point on the sky.
virtual Q_INVOKABLE Projection type() const =0
Return the type of this projection.
The sky coordinates of a point in the sky.
void EquatorialToHorizontal(const CachingDms *LST, const CachingDms *lat)
Determine the (Altitude, Azimuth) coordinates of the SkyPoint from its (RA, Dec) coordinates,...
This is just a container that holds information needed to do projections.
bool fillGround
If the ground is filled, then points below horizon are invisible.
const double & Degrees() const
QString i18n(const char *text, const TYPE &arg...)
qint64 elapsed() const const
QImage copy(const QRect &rectangle) const const
void fill(Qt::GlobalColor color)
bool load(QIODevice *device, const char *format)
QRgb pixel(const QPoint &position) const const
void setPixel(const QPoint &position, uint index_or_rgb)
QStatusBar * statusBar() const const
void showMessage(const QString &message, int timeout)
QString arg(Args &&... args) const const
bool isEmpty() const const