/** \brief Activate a given canvas frame. * * Set the active canvas frame. All subsequent drawing operations will * be performed on that frame. The current painting context set by * caca_set_attr() is inherited. * * If the frame index is outside the canvas' frame range, nothing happens. * * If an error occurs, -1 is returned and \b errno is set accordingly: * - \c EINVAL Requested frame is out of range. * * \param cv A libcaca canvas * \param id The canvas frame to activate * \return 0 in case of success, -1 if an error occurred. */ int caca_set_frame(caca_canvas_t *cv, int id) { if(id < 0 || id >= cv->framecount) { seterrno(EINVAL); return -1; } /* Bail out if no operation is required */ if(id == cv->frame) return 0; _caca_save_frame_info(cv); cv->frame = id; _caca_load_frame_info(cv); if(!cv->dirty_disabled) caca_add_dirty_rect(cv, 0, 0, cv->width, cv->height); return 0; }
int caca_resize(caca_canvas_t *cv, int width, int height) { int x, y, f, old_width, old_height, new_size, old_size; old_width = cv->width; old_height = cv->height; old_size = old_width * old_height; _caca_save_frame_info(cv); /* Preload new width and height values into the canvas to optimise * dirty rectangle handling */ cv->width = width; cv->height = height; new_size = width * height; /* If width or height is smaller (or both), we have the opportunity to * reduce or even remove dirty rectangles */ if(width < old_width || height < old_height) _caca_clip_dirty_rect_list(cv); /* Step 1: if new area is bigger, resize the memory area now. */ if(new_size > old_size) { for(f = 0; f < cv->framecount; f++) { cv->frames[f].chars = realloc(cv->frames[f].chars, new_size * sizeof(uint32_t)); cv->frames[f].attrs = realloc(cv->frames[f].attrs, new_size * sizeof(uint32_t)); if(new_size && (!cv->frames[f].chars || !cv->frames[f].attrs)) { seterrno(ENOMEM); return -1; } } } /* Step 2: move line data if necessary. */ if(width == old_width) { /* Width did not change, which means we do not need to move data. */ ; } else if(width > old_width) { /* New width is bigger than old width, which means we need to * copy lines starting from the bottom of the screen otherwise * we will overwrite information. */ for(f = 0; f < cv->framecount; f++) { uint32_t *chars = cv->frames[f].chars; uint32_t *attrs = cv->frames[f].attrs; for(y = height < old_height ? height : old_height; y--; ) { uint32_t attr = cv->frames[f].curattr; for(x = old_width; x--; ) { chars[y * width + x] = chars[y * old_width + x]; attrs[y * width + x] = attrs[y * old_width + x]; } /* Zero the end of the line */ for(x = width - old_width; x--; ) { chars[y * width + old_width + x] = (uint32_t)' '; attrs[y * width + old_width + x] = attr; } } } if(!cv->dirty_disabled) caca_add_dirty_rect(cv, old_width, 0, width - old_width, old_height); } else { /* New width is smaller. Copy as many lines as possible. Ignore * the first line, it is already in place. */ int lines = height < old_height ? height : old_height; for(f = 0; f < cv->framecount; f++) { uint32_t *chars = cv->frames[f].chars; uint32_t *attrs = cv->frames[f].attrs; for(y = 1; y < lines; y++) { for(x = 0; x < width; x++) { chars[y * width + x] = chars[y * old_width + x]; attrs[y * width + x] = attrs[y * old_width + x]; } } } } /* Step 3: fill the bottom of the new screen if necessary. */ if(height > old_height) { for(f = 0; f < cv->framecount; f++) { uint32_t *chars = cv->frames[f].chars; uint32_t *attrs = cv->frames[f].attrs; uint32_t attr = cv->frames[f].curattr; /* Zero the bottom of the screen */ for(x = (height - old_height) * width; x--; ) { chars[old_height * width + x] = (uint32_t)' '; attrs[old_height * width + x] = attr; } } if(!cv->dirty_disabled) caca_add_dirty_rect(cv, 0, old_height, old_width, height - old_height); } /* If both width and height are larger, there is a new dirty rectangle * that needs to be created in the lower right corner. */ if(!cv->dirty_disabled && width > old_width && height > old_height) caca_add_dirty_rect(cv, old_width, old_height, width - old_width, height - old_height); /* Step 4: if new area is smaller, resize memory area now. */ if(new_size < old_size) { for(f = 0; f < cv->framecount; f++) { cv->frames[f].chars = realloc(cv->frames[f].chars, new_size * sizeof(uint32_t)); cv->frames[f].attrs = realloc(cv->frames[f].attrs, new_size * sizeof(uint32_t)); if(new_size && (!cv->frames[f].chars || !cv->frames[f].attrs)) { seterrno(ENOMEM); return -1; } } } /* Set new size */ for(f = 0; f < cv->framecount; f++) { if(cv->frames[f].x > (int)width) cv->frames[f].x = width; if(cv->frames[f].y > (int)height) cv->frames[f].y = height; cv->frames[f].width = width; cv->frames[f].height = height; } /* Reset the current frame shortcuts */ _caca_load_frame_info(cv); return 0; }
/** \brief Rotate and stretch a canvas, 90 degrees clockwise. * * Apply a 270-degree transformation to a canvas, choosing characters * that look like the rotated version wherever possible. Some characters * will stay unchanged by the process, some others will be replaced by * close equivalents. Fullwidth characters will be lost. The operation is * not guaranteed to be reversible at all. * * Note that the width and height of the canvas are swapped, causing its * aspect ratio to look stretched. * * If an error occurs, -1 is returned and \b errno is set accordingly: * - \c EBUSY The canvas is in use by a display driver and cannot be rotated. * - \c ENOMEM Not enough memory to allocate the new canvas size. If this * happens, the previous canvas handle is still valid. * * \param cv The canvas to rotate right. * \return 0 in case of success, -1 if an error occurred. */ int caca_stretch_right(caca_canvas_t *cv) { uint32_t *newchars, *newattrs; int x, y; if(cv->refcount) { seterrno(EBUSY); return -1; } /* Save the current frame shortcuts */ _caca_save_frame_info(cv); newchars = malloc(cv->width * cv->height * sizeof(uint32_t)); if(!newchars) { seterrno(ENOMEM); return -1; } newattrs = malloc(cv->width * cv->height * sizeof(uint32_t)); if(!newattrs) { free(newchars); seterrno(ENOMEM); return -1; } for(y = 0; y < cv->height; y++) { for(x = 0; x < cv->width; x++) { uint32_t ch, attr; ch = cv->chars[cv->width * y + x]; attr = cv->attrs[cv->width * y + x]; /* FIXME: do something about fullwidth characters */ ch = rightchar(ch); newchars[cv->height * x + cv->height - 1 - y] = ch; newattrs[cv->height * x + cv->height - 1 - y] = attr; } } free(cv->chars); free(cv->attrs); /* Swap X and Y information */ x = cv->frames[cv->frame].x; y = cv->frames[cv->frame].y; cv->frames[cv->frame].x = cv->height - 1 - y; cv->frames[cv->frame].y = x; x = cv->frames[cv->frame].handlex; y = cv->frames[cv->frame].handley; cv->frames[cv->frame].handlex = cv->height - 1 - y; cv->frames[cv->frame].handley = x; cv->frames[cv->frame].width = cv->height; cv->frames[cv->frame].height = cv->width; cv->frames[cv->frame].chars = newchars; cv->frames[cv->frame].attrs = newattrs; /* Reset the current frame shortcuts */ _caca_load_frame_info(cv); caca_add_dirty_rect(cv, 0, 0, cv->width, cv->height); return 0; }
/** \brief Rotate a canvas, 90 degrees counterclockwise. * * Apply a 90-degree transformation to a canvas, choosing characters * that look like the rotated version wherever possible. Characters cells * are rotated two-by-two. Some characters will stay unchanged by the * process, some others will be replaced by close equivalents. Fullwidth * characters at odd horizontal coordinates will be lost. The operation is * not guaranteed to be reversible at all. * * Note that the width of the canvas is divided by two and becomes the * new height. Height is multiplied by two and becomes the new width. If * the original width is an odd number, the division is rounded up. * * If an error occurs, -1 is returned and \b errno is set accordingly: * - \c EBUSY The canvas is in use by a display driver and cannot be rotated. * - \c ENOMEM Not enough memory to allocate the new canvas size. If this * happens, the previous canvas handle is still valid. * * \param cv The canvas to rotate right. * \return 0 in case of success, -1 if an error occurred. */ int caca_rotate_right(caca_canvas_t *cv) { uint32_t *newchars, *newattrs; int x, y, w2, h2; if(cv->refcount) { seterrno(EBUSY); return -1; } /* Save the current frame shortcuts */ _caca_save_frame_info(cv); w2 = (cv->width + 1) / 2; h2 = cv->height; newchars = malloc(w2 * h2 * 2 * sizeof(uint32_t)); if(!newchars) { seterrno(ENOMEM); return -1; } newattrs = malloc(w2 * h2 * 2 * sizeof(uint32_t)); if(!newattrs) { free(newchars); seterrno(ENOMEM); return -1; } for(y = 0; y < h2; y++) { for(x = 0; x < w2; x++) { uint32_t pair[2], attr1, attr2; pair[0] = cv->chars[cv->width * y + x * 2]; attr1 = cv->attrs[cv->width * y + x * 2]; if((cv->width & 1) && x == w2 - 1) { /* Special case: odd column */ pair[1] = ' '; attr2 = attr1; } else { pair[1] = cv->chars[cv->width * y + x * 2 + 1]; attr2 = cv->attrs[cv->width * y + x * 2 + 1]; } /* If one of the characters is a space, we simply ignore * its colour attributes. Otherwise the resulting characters * may have totally wrong colours. */ if(pair[0] == ' ') attr1 = attr2; else if(pair[1] == ' ') attr2 = attr1; rightpair(pair); newchars[(h2 * x + h2 - 1 - y) * 2] = pair[0]; newattrs[(h2 * x + h2 - 1 - y) * 2] = attr1; newchars[(h2 * x + h2 - 1 - y) * 2 + 1] = pair[1]; newattrs[(h2 * x + h2 - 1 - y) * 2 + 1] = attr2; } } free(cv->chars); free(cv->attrs); /* Swap X and Y information */ x = cv->frames[cv->frame].x; y = cv->frames[cv->frame].y; cv->frames[cv->frame].x = (cv->height - 1 - y) * 2; cv->frames[cv->frame].y = x / 2; x = cv->frames[cv->frame].handlex; y = cv->frames[cv->frame].handley; cv->frames[cv->frame].handlex = (cv->height - 1 - y) * 2; cv->frames[cv->frame].handley = x / 2; cv->frames[cv->frame].width = cv->height * 2; cv->frames[cv->frame].height = (cv->width + 1) / 2; cv->frames[cv->frame].chars = newchars; cv->frames[cv->frame].attrs = newattrs; /* Reset the current frame shortcuts */ _caca_load_frame_info(cv); if(!cv->dirty_disabled) caca_add_dirty_rect(cv, 0, 0, cv->width, cv->height); return 0; }