blob: 5fdf4ca1f3253b4b59369938f46ae4165a784770 [file] [log] [blame]
/* SPDX-License-Identifier: LGPL-2.1 */
/*
* Copyright (C) 2018 VMware Inc, Yordan Karadzhov (VMware) <y.karadz@gmail.com>
*/
/**
* @file libkshark-plot.c
* @brief Basic tools for OpenGL plotting.
*/
// C
#ifndef _GNU_SOURCE
/** Use GNU C Library. */
#define _GNU_SOURCE
#endif // _GNU_SOURCE
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
// KernelShark
#include "libkshark-plot.h"
/*
* STB TrueType (single-file public domain library)
* https://github.com/nothings/stb
*/
/** Generate implementation. */
#define STB_TRUETYPE_IMPLEMENTATION
/** Make the implementation private. */
#define STBTT_STATIC
#include "stb_truetype.h"
#ifdef GLUT_FOUND
#include <GL/freeglut.h>
/**
* @brief Create an empty scene for drawing.
*
* @param width: Width of the screen window in pixels.
* @param height: Height of the screen window in pixels.
*/
void ksplot_make_scene(int width, int height)
{
/* Set Display mode. */
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
/* Prevent the program from exiting when a window is closed. */
glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE,
GLUT_ACTION_GLUTMAINLOOP_RETURNS);
/* Set window size. */
glutInitWindowSize(width, height);
/* Set window position on screen. */
glutInitWindowPosition(50, 50);
/* Open the screen window. */
glutCreateWindow("KernelShark Plot");
ksplot_resize_opengl(width, height);
}
#endif // GLUT_FOUND
/**
* @brief Initialize OpenGL.
*
* @param dpr: Device Pixel Ratio.
*/
void ksplot_init_opengl(int dpr)
{
glDisable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glDisable(GL_COLOR_MATERIAL);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_POLYGON_SMOOTH);
glLineWidth(1.5 * dpr);
glPointSize(2.5 * dpr);
glClearColor(1, 1, 1, 1); // White
}
/**
* @brief To be called whenever the OpenGL window has been resized.
*
* @param width: Width of the screen window in pixels.
* @param height: Height of the screen window in pixels.
*/
void ksplot_resize_opengl(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
/*
* Set the origin of the coordinate system to be the top left corner.
* The "Y" coordinate is inverted.
*/
gluOrtho2D(0, width, height, 0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
/**
* @brief Draw a point.
*
* @param p: Input location for the point object.
* @param col: The color of the point.
* @param size: The size of the point.
*/
void ksplot_draw_point(const struct ksplot_point *p,
const struct ksplot_color *col,
float size)
{
if (!p || !col || size < .5f)
return;
glPointSize(size);
glBegin(GL_POINTS);
glColor3ub(col->red, col->green, col->blue);
glVertex2i(p->x, p->y);
glEnd();
}
/**
* @brief Draw a line.
*
* @param a: Input location for the first finishing point of the line.
* @param b: Input location for the second finishing point of the line.
* @param col: The color of the line.
* @param size: The size of the line.
*/
void ksplot_draw_line(const struct ksplot_point *a,
const struct ksplot_point *b,
const struct ksplot_color *col,
float size)
{
if (!a || !b || !col || size < .5f)
return;
glLineWidth(size);
glBegin(GL_LINES);
glColor3ub(col->red, col->green, col->blue);
glVertex2i(a->x, a->y);
glVertex2i(b->x, b->y);
glEnd();
}
/**
* @brief Draw the a polyline.
*
* @param points: Input location for the array of points defining the polyline.
* @param n_points: The size of the array of points.
* @param col: The color of the polyline.
* @param size: The size of the polyline.
*/
void ksplot_draw_polyline(const struct ksplot_point *points,
size_t n_points,
const struct ksplot_color *col,
float size)
{
if (!points || !n_points || !col || size < .5f)
return;
/* Loop over the points of the polygon and draw connecting lines. */
for(size_t i = 1; i < n_points; ++i)
ksplot_draw_line(&points[i - 1],
&points[i],
col,
size);
}
/**
* @brief Draw a polygon.
*
* @param points: Input location for the array of points defining the polygon.
* @param n_points: The size of the array of points.
* @param col: The color of the polygon.
* @param size: The size of the polygon.
*/
void ksplot_draw_polygon(const struct ksplot_point *points,
size_t n_points,
const struct ksplot_color *col,
float size)
{
if (!points || !n_points || !col || size < .5f)
return;
if (n_points == 1) {
ksplot_draw_point(points, col, size);
return;
}
if (n_points == 2) {
ksplot_draw_line(points, points + 1, col, size);
return;
}
/* Draw a Triangle Fan. */
glBegin(GL_TRIANGLE_FAN);
glColor3ub(col->red, col->green, col->blue);
for (size_t i = 0; i < n_points; ++i)
glVertex2i(points[i].x, points[i].y);
glVertex2i(points[0].x, points[0].y);
glEnd();
}
/**
* @brief Draw the contour of a polygon.
*
* @param points: Input location for the array of points defining the polygon.
* @param n_points: The size of the array of points.
* @param col: The color of the polygon.
* @param size: The size of the polygon.
*/
void ksplot_draw_polygon_contour(const struct ksplot_point *points,
size_t n_points,
const struct ksplot_color *col,
float size)
{
if (!points || !n_points || !col || size < .5f)
return;
/* Loop over the points of the polygon and draw a polyline. */
ksplot_draw_polyline(points, n_points, col, size);
/* Close the contour. */
ksplot_draw_line(&points[0],
&points[n_points - 1],
col,
size);
}
/**
* @brief Find a TrueType font file.
*
* @param font_family: The family name of the font.
* @param font_name: The name of the font file without the extention.
*
* @returns A string containing the absolute path to the TrueType font file
* on success, or NULL on failure. The user is responsible for freeing
* the string.
*/
char *ksplot_find_font_file(const char *font_family, const char *font_name)
{
char buffer[1024], *end;
char *cmd = NULL;
int ret;
FILE *f;
/*
* This is sorta a hack.
* FIXME: Do this a bit more properly.
*/
ret = asprintf(&cmd, "fc-list \'%s\' |grep %s.ttf", font_family,
font_name);
if (ret <= 0)
goto fail;
f = popen(cmd, "r");
free(cmd);
if (f == NULL)
goto fail;
end = fgets(buffer, sizeof(buffer), f);
pclose(f);
if (!end)
goto fail;
end = strchr(buffer, ':');
if (!end)
goto fail;
return strndup(buffer, end - buffer);
fail:
fprintf(stderr, "Failed to find font file.\n" );
return NULL;
}
/** The size of the bitmap matrix used to load the font. */
#define KS_FONT_BITMAP_SIZE 1024
/**
* @brief Initialize a font.
*
* @param font: Output location for the font descriptor.
* @param size: The size of the font.
* @param file: Input location for the truetype font file.
*/
bool ksplot_init_font(struct ksplot_font *font, float size, const char *file)
{
unsigned char bitmap[KS_FONT_BITMAP_SIZE * KS_FONT_BITMAP_SIZE];
int ascent, descent, line_gap, lsb;
ssize_t buff_size, ret;
unsigned char *buffer;
stbtt_fontinfo info;
FILE *font_file;
struct stat st;
float scale;
ret = stat(file, &st);
if (ret < 0) {
fprintf(stderr, "Font file %s not found.\n", file);
return NULL;
}
font_file = fopen(file, "rb");
if (!font_file) {
fprintf(stderr, "Failed to open font file!\n");
return false;
}
/* Get the size of the file. */
buff_size = st.st_size;
buffer = malloc(buff_size);
if (!buffer) {
fprintf(stderr, "Failed to allocat memory for font!\n");
goto close_file;
}
ret = fread(buffer, buff_size, 1, font_file);
fclose(font_file);
if (ret == 0) {
fprintf(stderr, "Failed to read from font file!\n");
goto free_buffer;
}
if (!stbtt_InitFont(&info, buffer, 0)) {
fprintf(stderr, "Failed to init font!\n");
goto free_buffer;
}
/* Get the font's metrics. */
scale = stbtt_ScaleForMappingEmToPixels(&info, size);
stbtt_GetFontVMetrics(&info, &ascent, &descent, &line_gap);
if (line_gap == 0)
line_gap = - descent;
/*
* Calculate the dimensions of the font. Note that the descent has
* a negative value.
*/
font->height = (- descent + ascent + line_gap) * scale;
font->base = (- descent + line_gap / 2) * scale;
font->size = size;
/*
* The width of the 'z' character will be considered as an average
* character width.
*/
stbtt_GetCodepointHMetrics(&info, 'z', &font->char_width, &lsb);
font->char_width *= scale;
ret = stbtt_BakeFontBitmap(buffer,
0,
size,
bitmap,
KS_FONT_BITMAP_SIZE,
KS_FONT_BITMAP_SIZE,
KS_SPACE_CHAR,
KS_N_CHAR,
font->cdata);
if (ret <= 0) {
fprintf(stderr, "Failed to bake font bitmap!\n");
goto free_buffer;
}
free(buffer);
glGenTextures(1, &font->texture_id);
glBindTexture(GL_TEXTURE_2D, font->texture_id);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_ALPHA,
KS_FONT_BITMAP_SIZE,
KS_FONT_BITMAP_SIZE,
0,
GL_ALPHA,
GL_UNSIGNED_BYTE,
bitmap);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
return true;
close_file:
fclose(font_file);
free_buffer:
free(buffer);
return false;
}
/**
* @brief Print(draw) a text.
*
* @param font: Intput location for the font descriptor.
* @param col: The color of the polygon.
* @param x: Horizontal coordinate of the beginning of the text.
* @param y: Verticalal coordinate of the beginning of the text.
* @param text: The text to be drawn.
*/
void ksplot_print_text(const struct ksplot_font *font,
const struct ksplot_color *col,
float x, float y,
const char *text)
{
glEnable(GL_TEXTURE_2D);
/* Set the color of the text. */
if (col)
glColor3ub(col->red, col->green, col->blue);
else
glColor3ub(0, 0, 0); // Black
glBindTexture(GL_TEXTURE_2D, font->texture_id);
glBegin(GL_QUADS);
for (; *text; ++text) {
if (*text < KS_SPACE_CHAR && *text > KS_TILDA_CHAR)
continue;
stbtt_aligned_quad quad;
/* "x" is incremented here to a new position. */
stbtt_GetBakedQuad(font->cdata,
KS_FONT_BITMAP_SIZE,
KS_FONT_BITMAP_SIZE,
*text - KS_SPACE_CHAR,
&x, &y,
&quad,
1);
glTexCoord2f(quad.s0, quad.t1);
glVertex2f(quad.x0, quad.y1);
glTexCoord2f(quad.s1, quad.t1);
glVertex2f(quad.x1, quad.y1);
glTexCoord2f(quad.s1, quad.t0);
glVertex2f(quad.x1, quad.y0);
glTexCoord2f(quad.s0, quad.t0);
glVertex2f(quad.x0, quad.y0);
}
glEnd();
glDisable(GL_TEXTURE_2D);
}