#include #include #include #include #include #include #include using namespace std; #include "body.h" #include "findFile.h" #include "keywords.h" #include "Options.h" #include "parseColor.h" #include "ParseGeom.h" #include "PlanetProperties.h" #include "xpDefines.h" #include "xpUtil.h" #include "DisplayBase.h" #include "libimage/Image.h" extern TextRenderer *getTextRenderer(DisplayBase *display); DisplayBase::DisplayBase(const int tr) : times_run(tr) { textRenderer_ = getTextRenderer(this); } DisplayBase::~DisplayBase() { delete [] rgb_data; delete [] alpha; delete textRenderer_; } void DisplayBase::Font(const string &fontname) { textRenderer_->Font(fontname); } void DisplayBase::FontSize(const int size) { textRenderer_->FontSize(size); } void DisplayBase::getTextBox(int &textWidth, int &textHeight) { textRenderer_->TextBox(textWidth, textHeight); } // Remember to call FreeText() when done with the text operation void DisplayBase::setText(const string &text) { textRenderer_->SetText(text); } void DisplayBase::FreeText() { textRenderer_->FreeText(); } // x and y are the center left coordinates of the string void DisplayBase::DrawOutlinedText(const int x, int y, const string &text, const unsigned char color[3]) { textRenderer_->DrawOutlinedText(x, y, text, color); } void DisplayBase::drawLabelLine(int ¤tX, int ¤tY, const string &text) { setText(text); int textWidth, textHeight; getTextBox(textWidth, textHeight); FreeText(); Options *options = Options::getInstance(); if (options->LabelMask() & XNegative) { currentX = (options->LabelX() + width_ - 2 - textWidth); } DrawOutlinedText(currentX, currentY, text, options->Color()); currentY += textRenderer_->FontHeight(); } void DisplayBase::drawLabel(PlanetProperties *planetProperties[]) { Options *options = Options::getInstance(); if (!options->DrawLabel()) return; vector labelLines; const body target = options->getTarget(); const body origin = options->getOrigin(); string lookAt; if (options->LabelString().empty()) { if (options->TargetMode() != LOOKAT) { lookAt.assign("Looking at "); string viewTarget; string viewOrigin; if (options->Projection() == MULTIPLE) { viewTarget.assign(planetProperties[target]->Name()); switch (options->OriginMode()) { case ABOVE: viewOrigin.assign(" from above"); break; case BELOW: viewOrigin.assign(" from below"); break; case BODY: if (options->OppositeSide()) { viewTarget.assign(planetProperties[origin]->Name()); viewOrigin.assign(" from behind "); viewOrigin += planetProperties[target]->Name(); } else { viewOrigin.assign(" from "); viewOrigin += planetProperties[origin]->Name(); } break; case LBR: default: break; } } else { viewTarget.assign(planetProperties[target]->Name()); } lookAt += viewTarget; lookAt += viewOrigin; } } else { lookAt.assign(options->LabelString()); for (unsigned int i = 0; i < lookAt.size() - 1; i++) { if (lookAt[i] == '%') { switch (lookAt[i+1]) { case 't': if (target < RANDOM_BODY) lookAt.replace(i, 2, planetProperties[target]->Name()); break; case 'o': if (origin < RANDOM_BODY) lookAt.replace(i, 2, planetProperties[origin]->Name()); break; case '%': lookAt.erase(i, 1); break; } } } } time_t tv_sec = options->TVSec(); string timeString; if (tv_sec == (time_t) (-1)) { int year, month, day, hour, min; double sec; double jd = options->JulianDay(); fromJulian(jd, year, month, day, hour, min, sec); char timeBuffer[MAX_LINE_LENGTH]; memset(timeBuffer, 0, MAX_LINE_LENGTH); snprintf(timeBuffer, MAX_LINE_LENGTH, "%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d UTC", year, month, day, hour, min, static_cast (floor(sec))); timeString.assign(timeBuffer); } else { char *tzEnv = getenv("TZ"); string tzSave(""); if (options->DrawUTCLabel()) { if (tzEnv != NULL) { tzSave = "TZ="; tzSave += tzEnv; } putenv("TZ=UTC"); tzset(); } timeString.assign(options->DateFormat()); // run date string through strftime() and convert to UTF-8 strftimeUTF8(timeString); if (options->DrawUTCLabel()) { if (tzEnv == NULL) removeFromEnvironment("TZ"); else putenv((char *) tzSave.c_str()); tzset(); } } if (!lookAt.empty()) labelLines.push_back(lookAt); labelLines.push_back(timeString); if (options->TargetMode() != LOOKAT) { char obsString[MAX_LINE_LENGTH]; double obsLatDeg = options->Latitude() / deg_to_rad; double obsLonDeg = options->Longitude() / deg_to_rad; if (target == EARTH || target == MOON) { if (obsLonDeg > 180) obsLonDeg -= 360; snprintf(obsString, MAX_LINE_LENGTH, "obs %4.1f %c %5.1f %c", fabs(obsLatDeg), ((obsLatDeg < 0) ? 'S' : 'N'), fabs(obsLonDeg), ((obsLonDeg < 0) ? 'W' : 'E')); } else { if (obsLonDeg < 0) obsLonDeg += 360; snprintf(obsString, MAX_LINE_LENGTH,"obs %4.1f %c %5.1f", fabs(obsLatDeg), ((obsLatDeg < 0) ? 'S' : 'N'), obsLonDeg); } labelLines.push_back(obsString); if (target != SUN) { char sunString[MAX_LINE_LENGTH]; double sunLatDeg = options->SunLat() / deg_to_rad; double sunLonDeg = options->SunLon() / deg_to_rad; if (target == EARTH || target == MOON) { if (sunLonDeg > 180) sunLonDeg -= 360; snprintf(sunString, MAX_LINE_LENGTH, "sun %4.1f %c %5.1f %c", fabs(sunLatDeg), ((sunLatDeg < 0) ? 'S' : 'N'), fabs(sunLonDeg), ((sunLonDeg < 0) ? 'W' : 'E')); } else { if (sunLonDeg < 0) sunLonDeg += 360; snprintf(sunString, MAX_LINE_LENGTH,"sun %4.1f %c %5.1f", fabs(sunLatDeg), ((sunLatDeg < 0) ? 'S' : 'N'), sunLonDeg); } labelLines.push_back(sunString); } } if (options->Projection() == MULTIPLE) { char fovCString[MAX_LINE_LENGTH]; double fov = options->FieldOfView() / deg_to_rad; if (fov > 1) snprintf(fovCString, MAX_LINE_LENGTH, "fov %.1f degrees", fov); else if (fov * 60 > 1) { fov *= 60; snprintf(fovCString, MAX_LINE_LENGTH, "fov %.1f arc minutes", fov); } else if (fov * 3600 > 1) { fov *= 3600; snprintf(fovCString, MAX_LINE_LENGTH, "fov %.1f arc seconds", fov); } else { fov *= 3600000; snprintf(fovCString, MAX_LINE_LENGTH, "fov %.1f milliarc seconds", fov); } double oX, oY, oZ; options->getOrigin(oX, oY, oZ); double tX, tY, tZ; options->getTarget(tX, tY, tZ); const double deltX = tX - oX; const double deltY = tY - oY; const double deltZ = tZ - oZ; double targetDist = AU_to_km * sqrt(deltX*deltX + deltY*deltY + deltZ*deltZ); char distString[MAX_LINE_LENGTH]; if (targetDist < 1e6) { snprintf(distString, MAX_LINE_LENGTH, "dist %.0f km", targetDist); } else if (targetDist < 1e9) { targetDist /= 1e6; snprintf(distString, MAX_LINE_LENGTH, "dist %.1f million km", targetDist); } else { targetDist /= 1e9; snprintf(distString, MAX_LINE_LENGTH, "dist %.1f billion km", targetDist); } labelLines.push_back(fovCString); if (options->getTarget() != ALONG_PATH) labelLines.push_back(distString); if (options->TargetMode() != LOOKAT && target != SUN) { char illumString[MAX_LINE_LENGTH]; const double illumination = 50 * (ndot(tX, tY, tZ, deltX, deltY, deltZ) + 1); snprintf(illumString, MAX_LINE_LENGTH, "illumination %.1f %%", illumination); labelLines.push_back(illumString); } } int labelX = options->LabelX(); int labelY = options->LabelY() + textRenderer_->FontHeight()/2; if (options->LabelMask() & YNegative) { labelY += (height_ - labelLines.size() * textRenderer_->FontHeight()); } for (unsigned int i = 0; i < labelLines.size(); i++) { if (!labelLines[i].empty()) drawLabelLine(labelX, labelY, labelLines[i]); } } // Set the pixel value to { value, value, value } void DisplayBase::setPixel(const int x, const int y, const unsigned int value) { if (x < 0 || x >= width_ || y < 0 || y >= height_) return; unsigned char *background = rgb_data + 3*(y*width_ + x); memset(background, value, 3); if (alpha != NULL) alpha[y*width_ + x] = 255; } // Given floating point pixel values, spread the pixel around its // neighbors void DisplayBase::setPixel(const double X, const double Y, const unsigned char color[3]) { if (X < 0 || X >= width_ || Y < 0 || Y >= height_) return; const int x0 = static_cast (floor(X)); const int y0 = static_cast (floor(Y)); int ipos[4]; ipos[0] = y0 * width_ + x0; ipos[1] = ipos[0] + 1; ipos[2] = ipos[0] + width_; ipos[3] = ipos[2] + 1; const double t = X - x0; const double u = 1 - (Y - y0); double weight[4]; getWeights(t, u, weight); setPixel(x0, y0, color, weight[0]); setPixel(x0+1, y0, color, weight[1]); setPixel(x0, y0+1, color, weight[2]); setPixel(x0+1, y0+1, color, weight[3]); } void DisplayBase::setPixel(const int x, const int y, const unsigned char pixel[3]) { setPixel(x, y, pixel, 1.0); } void DisplayBase::setPixel(const int x, const int y, const unsigned char p[3], const double opacity) { if (x < 0 || x >= width_ || y < 0 || y >= height_) return; unsigned char *background = rgb_data + 3*(y*width_ + x); unsigned char pixel[3]; memcpy(pixel, p, 3); if (opacity < 1) { for (int i = 0; i < 3; i++) pixel[i] = (unsigned char) (opacity * p[i] + (1 - opacity) * background[i]); if (alpha != NULL) alpha[y*width_ + x] = (unsigned char) (opacity * 255); } else { if (alpha != NULL) alpha[y*width_ + x] = 255; } memcpy(background, pixel, 3); } void DisplayBase::getPixel(const int x, const int y, unsigned char pixel[3]) const { if (x < 0 || x >= width_ || y < 0 || y >= height_) return; memcpy(pixel, rgb_data + 3*(y*width_ + x), 3); } void DisplayBase::SetBackground(const int width, const int height, unsigned char *rgb) { Options *options = Options::getInstance(); string backgroundFile(options->Background()); if (!backgroundFile.empty()) { // First check if requesting a color unsigned char color[3]; string failed; parseColor(backgroundFile, color, failed); if (failed.empty()) { int ipos = 0; for (int i = 0; i < width * height; i++) { for (int j = 0; j < 3; j++) memcpy(rgb + ipos++, &color[j], 1); } } else { // look for an image file Image *image = new Image; bool foundFile = findFile(backgroundFile, "images"); if (foundFile) foundFile = image->Read(backgroundFile.c_str()); if (foundFile) { if ((image->Width() != width) || (image->Height() != height)) { ostringstream errStr; errStr << "For better performance, " << "background image should " << "be the same size as the output image\n"; xpWarn(errStr.str(), __FILE__, __LINE__); image->Resize(width, height); } memcpy(rgb, image->getRGBData(), 3 * width * height); } delete image; } } else { if (options->ProjectionMode() != MULTIPLE) { // add random stars int numStars = static_cast (width * height * options->StarFreq()); for (int i = 0; i < numStars; i++) { int j = random() % width; int k = random() % height; int brightness = random() % 256; memset(rgb + 3 * (k * width + j), brightness, 3); } } } } void DisplayBase::allocateRGBData() { area_ = width_ * height_; rgb_data = new unsigned char [3 * area_]; memset(rgb_data, 0, 3 * area_); alpha = NULL; Options *options = Options::getInstance(); if (options->TransPNG()) { alpha = new unsigned char [area_]; memset(alpha, 0, area_); } // If a background image is specified along with -geometry and // we're drawing to the root window, it will only be overlaid on // the true root window. The sub-image won't have the background // image. if (options->DisplayMode() != ROOT || !options->GeometrySelected()) SetBackground(width_, height_, rgb_data); } string DisplayBase::TmpDir() { Options *options = Options::getInstance(); string returnstring = options->TmpDir(); if (returnstring.empty()) { char *tmpdir = getenv("TMPDIR"); if (tmpdir == NULL) returnstring.assign("/tmp"); else returnstring.assign(tmpdir); } return(returnstring); } // If -geometry is specified, overlay the image on the root window. void DisplayBase::PlaceImageOnRoot() { Options *options = Options::getInstance(); if (!options->GeometrySelected()) return; const int area = fullWidth_ * fullHeight_; unsigned char *tmp = new unsigned char [ 3 * area ]; memset(tmp, 0, 3 * area); SetBackground(fullWidth_, fullHeight_, tmp); int x = options->getWindowX(); int y = options->getWindowY(); if (options->GeometryMask() & XNegative) x += (fullWidth_ - width_); if (options->GeometryMask() & YNegative) y += (fullHeight_ - height_); const int xmin = (x < 0) ? 0 : x; const int ymin = (y < 0) ? 0 : y; const int xmax = (x + width_ > fullWidth_) ? fullWidth_ : x + width_; const int ymax = (y + height_ > fullHeight_) ? fullHeight_ : y + height_; for (int trueY = ymin; trueY < ymax; trueY++) { unsigned char *trueP = tmp + 3 * trueY * fullWidth_; const int windowY = trueY - ymin; unsigned char *windowP = rgb_data + 3 * windowY * width_; for (int trueX = xmin; trueX < xmax; trueX++) { const int windowX = trueX - xmin; memcpy(trueP + 3 * trueX, windowP + 3 * windowX, 3); } } delete [] rgb_data; rgb_data = tmp; }