ENIGMA Forums

Contributing to ENIGMA => Function Peer Review => Topic started by: Seeker on June 18, 2013, 02:52:23 pm

Title: sprite_create_from_screen
Post by: Seeker on June 18, 2013, 02:52:23 pm
Hello, lovely people.

I put this together for you. The only items that need implementation are the "smooth" and "preload" params, but this is ready to run.

Enjoy.

Code: [Select]
/********************************************************************************\
**                                                                              **
**  Copyright (C) 2013 Seeker                                                   **
**                                                                              **
**  This file is a part of the ENIGMA Development Environment.                  **
**                                                                              **
**                                                                              **
**  ENIGMA is free software: you can redistribute it and/or modify it under the **
**  terms of the GNU General Public License as published by the Free Software   **
**  Foundation, version 3 of the license or any later version.                  **
**                                                                              **
**  This application and its source code is distributed AS-IS, WITHOUT ANY      **
**  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS   **
**  FOR A PARTICULAR PURPOSE. See the GNU General Public License for more       **
**  details.                                                                    **
**                                                                              **
**  You should have recieved a copy of the GNU General Public License along     **
**  with this code. If not, see <http://www.gnu.org/licenses/>                  **
**                                                                              **
**  ENIGMA is an environment designed to create games and other programs with a **
**  high-level, fully compilable language. Developers of ENIGMA or anything     **
**  associated with ENIGMA are in no way responsible for its users or           **
**  applications created by its users, or damages caused by the environment     **
**  or programs made in the environment.                                        **
**                                                                              **
\********************************************************************************/

// Includes

#include <string.h>

#include "Universal_System/nlpo2.h"



// Declarations

// gm7-
int sprite_create_from_screen(unsigned int x, unsigned int y, unsigned int w, unsigned int h, bool precise, bool transparent, bool smooth, bool preload, int xorig, int yorig);

// gm8+
int sprite_create_from_screen(unsigned int x, unsigned int y, unsigned int w, unsigned int h, bool removeback, bool smooth, int xorig, int yorig);



// Definitions

// NOTE: The only items that need implementation are the "smooth" and "preload" params
int sprite_create_from_screen(unsigned int x, unsigned int y, unsigned int w, unsigned int h, bool precise, bool transparent, bool smooth, bool preload, int xorig, int yorig)
{

static bool viewRedrawHack = false; // currently needed so this function can safely be called in a Draw event, since this function forces a screen_redraw() if view_enabled
if (view_enabled && viewRedrawHack) return -1;

//

w = max(w,1);
h = max(h,1);

int wFull = nlpo2dc(w) + 1,
hFull = nlpo2dc(h) + 1,
stride = wFull * 4;

//

unsigned char * swapline = new unsigned char[stride](),
              * pxdata = new unsigned char[stride*hFull]();

if (!swapline || !pxdata) return -1;

//

int viewportData[4];
glGetIntegerv(GL_VIEWPORT, viewportData);

if (view_enabled) // hacking views like the NSA
{
bool viewVisPrev[8];

for (int i = 0; i < 8; i++)
{
viewVisPrev[i] = view_visible[i];
view_visible[i] = (i == 0);
}

int wview0Prev = view_wview,
hview0Prev = view_hview;

view_wview = /*window_get_width()*/viewportData[2];
view_hview = /*window_get_height()*/viewportData[3];
viewRedrawHack = true;
        screen_redraw();
        viewRedrawHack = false;
view_wview = wview0Prev;
view_hview = hview0Prev;

for (int i = 0; i < 8; i++) view_visible[i] = viewVisPrev[i];
}

glReadPixels(x, /*window_get_height()*/viewportData[3] - y - hFull, wFull, hFull, GL_RGBA, GL_UNSIGNED_BYTE, pxdata);

//

for (int row = 0; row < hFull/2; row++) // flip pixels vertically
{
memcpy(swapline, pxdata + row*stride, stride);
memcpy(pxdata + row*stride, pxdata + (hFull-row-1)*stride, stride);
memcpy(pxdata + (hFull-row-1)*stride, swapline, stride);
}

delete[] swapline;

//

int bbl = 0,
bbt = 0,
bbr = w - 1,
bbb = h - 1;

if (transparent)
{
unsigned char* pxdataTrans = &pxdata[(h-1)*stride]; // transparency pixel data (bottom-left pixel)

bool bblGot = false,
bbtGot = false,
bbrGot = false,
bbbGot = false;

// remove transparent color, and get bbox top and bottom
for (int yy = 0; yy < h; yy++)
{
bool bbtRowEmpty = true,
bbbRowEmpty = true;

for (int xx = 0; xx < w; xx++)
{
int xo = xx*4,
bbtOffset = yy*stride + xo;

if (memcmp(&pxdata[bbtOffset], pxdataTrans, 3) == 0) pxdata[bbtOffset+3] = 0; // a transparent pixel
else bbtRowEmpty = false;

if (!bbbGot && memcmp(&pxdata[(h-1-yy)*stride + xo], pxdataTrans, 3) != 0) bbbRowEmpty = false;
}

if (!bbtGot)
{
if (bbtRowEmpty) bbt++;
else bbtGot = true;
}

if (!bbbGot)
{
if (bbbRowEmpty) bbb--;
else bbbGot = true;
}
}

// now just get bbox left and right
for (int xx = 0; xx < w; xx++)
{
bool bblColEmpty = true,
bbrColEmpty = true;

for (int yy = 0; yy < h; yy++)
{
int xo = xx*4 + 3;
if (!bblGot && pxdata[yy*stride + xo] != 0) bblColEmpty = false;
if (!bbrGot && pxdata[(h-1-yy)*stride + xo] != 0) bbrColEmpty = false;
}

if (!bblGot)
{
if (bblColEmpty) bbl++;
else bblGot = true;
}

if (!bbrGot)
{
if (bbrColEmpty) bbr--;
else bbrGot = true;
}

if (bblGot && bbrGot) break;
}

// if the entire image is transparent, doing this to keep bboxes from being off by 1 pixel
bbl = min(bbl, w - 1);
bbt = min(bbt, h - 1);
bbr = max(bbr, 0);
bbb = max(bbb, 0);
}

// show_message("l: " + string(bbl) + " t: " + string(bbt) + " r: " + string(bbr) + " b: " + string(bbb));

// mostly sprite_add_to_index() code from spritestruct.cpp follows, so may want to refactor

enigma::spritestructarray_reallocate();
enigma::sprite *ns = enigma::spritestructarray[enigma::sprite_idmax] = new enigma::sprite();

// if (!ns) return -1; //

ns->id = enigma::sprite_idmax;
ns->subcount = 1;
ns->width = w;
ns->height = h;

ns->bbox.bottom = bbb;
ns->bbox.left = bbl;
ns->bbox.top = bbt;
ns->bbox.right = bbr;

ns->bbox_relative.bottom = bbb - yorig;
ns->bbox_relative.left = bbl - xorig;
ns->bbox_relative.top = bbt - yorig;
ns->bbox_relative.right = bbr - xorig;

ns->xoffset = (int)xorig;
ns->yoffset = (int)yorig;

ns->texturearray = new int[1];
ns->texturearray[0] = enigma::graphics_create_texture(wFull, hFull, pxdata);

ns->texbordxarray = new double[1];
ns->texbordxarray[0] = (double) w/wFull;
ns->texbordyarray = new double[1];
ns->texbordyarray[0] = (double) h/hFull;

ns->colldata = new void*[1];
enigma::collision_type coll_type = precise ? enigma::ct_precise : enigma::ct_bbox;
ns->colldata[0] = get_collision_mask(ns, (unsigned char *)pxdata, coll_type);

delete[] pxdata;

//

return enigma::sprite_idmax++;

}

inline int sprite_create_from_screen(unsigned int x, unsigned int y, unsigned int w, unsigned int h, bool removeback, bool smooth, int xorig, int yorig)
{
return sprite_create_from_screen(x, y, w, h, /*precise*/removeback, /*transparent*/removeback, smooth, /*preload*/true, xorig, yorig);
}
Title: Re: sprite_create_from_screen
Post by: Josh @ Dreamland on June 18, 2013, 07:15:38 pm
Hi, there! Thanks much for the function; help is always welcome.

I'm concerned about a few points:
- Your view hack seems to redraw the first view, and only the first view, which means if this was called in the draw event, the game would loop, and that I can't draw a circle to the screen and create a sprite from it if view are enabled.
- You used window_get_height() - y for the y coordinate... are you sure this is necessary? Currently, the engine doesn't guarantee window_get_width/height to exist. So if that function needs it, we'll have to add that as a requirement in platforms_mandatory.h (https://github.com/enigma-dev/enigma-dev/blob/master/ENIGMAsystem/SHELL/Platforms/platforms_mandatory.h). Not a huge deal, but I'm not sure GM's/GL's screen coordinates are reversed.
- You didn't give your code a license, so I can't legally include it in the engine. Could you make a similar header to the one ENIGMA currently uses, with your screen name (or optionally, your full name and email)?

Anyway, thanks for the code! It's good to see that people can get the project working and start developing for it.

Cheers
Title: Re: sprite_create_from_screen
Post by: Seeker on June 19, 2013, 03:47:38 pm
Hey, Josh!

Good thinking. I edited the code with your suggestions. The function can now be used in a Draw event without infinitely looping when view_enabled.

Although, the view hack still forces a screen_redraw(), as I couldn't figure another way at the moment. So if someone uses the function in a Draw event with view_enabled, here are two examples of how they might do that:

Create Event (both examples):

Code: [Select]
createSprite = false;

Draw Event (example 1):

Code: [Select]
draw_set_color(c_blue);
draw_rectangle(0,0,32,32,0);
//
if (createSprite)
{
    createSprite = false;
    variant spr = sprite_create_from_screen(0,0,32,32,false,false,0,0);
    //
    if (sprite_exists(spr)) show_message("Do something with new sprite!");
}

Draw Event (example 2)

Code: [Select]
if (createSprite)
{
    draw_set_color(c_green);
    draw_rectangle(0,0,32,32,0);
    //
    variant spr = sprite_create_from_screen(0,0,32,32,false,false,0,0);
    createSprite = false; // Note how this line comes AFTER sprite_create_from_screen() in this example. If it didn't and view_enabled is true, the rectangle wouldn't be captured in the created sprite due to the forced screen_redraw() for views. See why???
    //
    if (sprite_exists(spr)) show_message("Do something with new sprite!");
}

In testing, this sprite_create_from_screen() function will actually capture the image in any one view, but I couldn't know about multiple views in one image, as it seems (in quick testing) enigma has a few kinks in rendering multiple views. For example, I just tried setting up two views side by side, splitting the screen in half, but the left viewport was always a garbled image.

For the window_get_width/height() part, I changed them to use the OpenGL viewport values directly. The "viewportData[3] - y - hFull" and pixel data flipping was necessary for me to get the correct result, but I know very little about OpenGL actually, so perhaps there's another way.

Thanks for the great work you've done so far with ENIGMA.
Title: Re: sprite_create_from_screen
Post by: Josh @ Dreamland on June 21, 2013, 05:25:21 am
I've been talking with the other devs, and I can't seem to get a straight answer regarding the way GM vs ENIGMA handles views, with respect to how this function treats views in GM.

Also, ENIGMA has licensing issues that I'm going to have to work with everyone to sort out before we start accepting code all over (right now it's been okay, because we have a very small number of frequent contributors).

The function [snip]sprite_save_from_screen[/snip] is already implemented, I believe. Is its behavior with views incorrect?
Title: Re: sprite_create_from_screen
Post by: Goombert on July 09, 2013, 04:00:44 am
Josh, I can tell you how Game Maker handles views, they suck, have a nice day. Seriously though, they are unbelievably glitchy in Game Maker, especially trying to get two of them to work simultaneously, you can never get the resolution correctly with them either, the screen resolution does not matter it is the rendering area resolution that people don't ever try to get correct, because it is freaking impossible.

@seeker, the licensing issue Josh speaks of is, we have most of our code licensed GPL and we need to work out an exception where people can sell games made with ENIGMA, but while trying to keep it from our project being stolen from us.