Home | History | Annotate | Download | only in paint
      1 /*
      2  * Copyright 2009, The Android Open Source Project
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *  * Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  *  * Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "PaintPlugin.h"
     27 
     28 #include <fcntl.h>
     29 #include <math.h>
     30 #include <string.h>
     31 
     32 extern NPNetscapeFuncs*         browser;
     33 extern ANPLogInterfaceV0        gLogI;
     34 extern ANPCanvasInterfaceV0     gCanvasI;
     35 extern ANPPaintInterfaceV0      gPaintI;
     36 extern ANPPathInterfaceV0       gPathI;
     37 extern ANPSurfaceInterfaceV0    gSurfaceI;
     38 extern ANPSystemInterfaceV0     gSystemI;
     39 extern ANPTypefaceInterfaceV0   gTypefaceI;
     40 extern ANPWindowInterfaceV0     gWindowI;
     41 
     42 ///////////////////////////////////////////////////////////////////////////////
     43 
     44 PaintPlugin::PaintPlugin(NPP inst) : SurfaceSubPlugin(inst) {
     45 
     46     m_isTouchActive = false;
     47     m_isTouchCurrentInput = true;
     48     m_activePaintColor = s_redColor;
     49 
     50     memset(&m_drawingSurface, 0, sizeof(m_drawingSurface));
     51     memset(&m_inputToggle,  0, sizeof(m_inputToggle));
     52     memset(&m_colorToggle, 0, sizeof(m_colorToggle));
     53     memset(&m_fullScreenToggle, 0, sizeof(m_fullScreenToggle));
     54     memset(&m_clearSurface,  0, sizeof(m_clearSurface));
     55 
     56     // initialize the drawing surface
     57     m_surface = NULL;
     58 
     59     // initialize the path
     60     m_touchPath = gPathI.newPath();
     61     if(!m_touchPath)
     62         gLogI.log(kError_ANPLogType, "----%p Unable to create the touch path", inst);
     63 
     64     // initialize the paint colors
     65     m_paintSurface = gPaintI.newPaint();
     66     gPaintI.setFlags(m_paintSurface, gPaintI.getFlags(m_paintSurface) | kAntiAlias_ANPPaintFlag);
     67     gPaintI.setColor(m_paintSurface, 0xFFC0C0C0);
     68     gPaintI.setTextSize(m_paintSurface, 18);
     69 
     70     m_paintButton = gPaintI.newPaint();
     71     gPaintI.setFlags(m_paintButton, gPaintI.getFlags(m_paintButton) | kAntiAlias_ANPPaintFlag);
     72     gPaintI.setColor(m_paintButton, 0xFFA8A8A8);
     73 
     74     // initialize the typeface (set the colors)
     75     ANPTypeface* tf = gTypefaceI.createFromName("serif", kItalic_ANPTypefaceStyle);
     76     gPaintI.setTypeface(m_paintSurface, tf);
     77     gTypefaceI.unref(tf);
     78 
     79     //register for touch events
     80     ANPEventFlags flags = kTouch_ANPEventFlag;
     81     NPError err = browser->setvalue(inst, kAcceptEvents_ANPSetValue, &flags);
     82     if (err != NPERR_NO_ERROR) {
     83         gLogI.log(kError_ANPLogType, "Error selecting input events.");
     84     }
     85 }
     86 
     87 PaintPlugin::~PaintPlugin() {
     88     gPathI.deletePath(m_touchPath);
     89     gPaintI.deletePaint(m_paintSurface);
     90     gPaintI.deletePaint(m_paintButton);
     91 
     92     setContext(NULL);
     93     destroySurface();
     94 }
     95 
     96 ANPCanvas* PaintPlugin::getCanvas(ANPRectI* dirtyRect) {
     97 
     98     ANPBitmap bitmap;
     99     JNIEnv* env = NULL;
    100     if (!m_surface || gVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK ||
    101         !gSurfaceI.lock(env, m_surface, &bitmap, dirtyRect)) {
    102             return NULL;
    103         }
    104 
    105     ANPCanvas* canvas = gCanvasI.newCanvas(&bitmap);
    106 
    107     // clip the canvas to the dirty rect b/c the surface is only required to
    108     // copy a minimum of the dirty rect and may copy more. The clipped canvas
    109     // however will never write to pixels outside of the clipped area.
    110     if (dirtyRect) {
    111         ANPRectF clipR;
    112         clipR.left = dirtyRect->left;
    113         clipR.top = dirtyRect->top;
    114         clipR.right = dirtyRect->right;
    115         clipR.bottom = dirtyRect->bottom;
    116         gCanvasI.clipRect(canvas, &clipR);
    117     }
    118 
    119     return canvas;
    120 }
    121 
    122 ANPCanvas* PaintPlugin::getCanvas(ANPRectF* dirtyRect) {
    123 
    124     ANPRectI newRect;
    125     newRect.left = (int) dirtyRect->left;
    126     newRect.top = (int) dirtyRect->top;
    127     newRect.right = (int) dirtyRect->right;
    128     newRect.bottom = (int) dirtyRect->bottom;
    129 
    130     return getCanvas(&newRect);
    131 }
    132 
    133 void PaintPlugin::releaseCanvas(ANPCanvas* canvas) {
    134     JNIEnv* env = NULL;
    135     if (m_surface && gVM->GetEnv((void**) &env, JNI_VERSION_1_4) == JNI_OK) {
    136         gSurfaceI.unlock(env, m_surface);
    137     }
    138     gCanvasI.deleteCanvas(canvas);
    139 }
    140 
    141 void PaintPlugin::drawCleanPlugin(ANPCanvas* canvas) {
    142     NPP instance = this->inst();
    143     PluginObject *obj = (PluginObject*) instance->pdata;
    144 
    145     // if no canvas get a locked canvas
    146     if (!canvas)
    147         canvas = getCanvas();
    148 
    149     if (!canvas)
    150         return;
    151 
    152     const float buttonWidth = 60;
    153     const float buttonHeight = 30;
    154     const int W = obj->window->width;
    155     const int H = obj->window->height;
    156 
    157     // color the plugin canvas
    158     gCanvasI.drawColor(canvas, 0xFFCDCDCD);
    159 
    160     // get font metrics
    161     ANPFontMetrics fontMetrics;
    162     gPaintI.getFontMetrics(m_paintSurface, &fontMetrics);
    163 
    164     // draw the input toggle button
    165     m_inputToggle.left = 5;
    166     m_inputToggle.top = H - buttonHeight - 5;
    167     m_inputToggle.right = m_inputToggle.left + buttonWidth;
    168     m_inputToggle.bottom = m_inputToggle.top + buttonHeight;
    169     gCanvasI.drawRect(canvas, &m_inputToggle, m_paintButton);
    170     const char* inputText = m_isTouchCurrentInput ? "Touch" : "Mouse";
    171     gCanvasI.drawText(canvas, inputText, strlen(inputText), m_inputToggle.left + 5,
    172                       m_inputToggle.top - fontMetrics.fTop, m_paintSurface);
    173 
    174     // draw the color selector button
    175     m_colorToggle.left = (W/3) - (buttonWidth/2);
    176     m_colorToggle.top = H - buttonHeight - 5;
    177     m_colorToggle.right = m_colorToggle.left + buttonWidth;
    178     m_colorToggle.bottom = m_colorToggle.top + buttonHeight;
    179     gCanvasI.drawRect(canvas, &m_colorToggle, m_paintButton);
    180     const char* colorText = getColorText();
    181     gCanvasI.drawText(canvas, colorText, strlen(colorText), m_colorToggle.left + 5,
    182                       m_colorToggle.top - fontMetrics.fTop, m_paintSurface);
    183 
    184     // draw the full-screen toggle button
    185     m_fullScreenToggle.left = ((W*2)/3) - (buttonWidth/2);
    186     m_fullScreenToggle.top = H - buttonHeight - 5;
    187     m_fullScreenToggle.right = m_fullScreenToggle.left + buttonWidth;
    188     m_fullScreenToggle.bottom = m_fullScreenToggle.top + buttonHeight;
    189     gCanvasI.drawRect(canvas, &m_fullScreenToggle, m_paintButton);
    190     const char* fullScreenText = "Full";
    191     gCanvasI.drawText(canvas, fullScreenText, strlen(fullScreenText),
    192                       m_fullScreenToggle.left + 5,
    193                       m_fullScreenToggle.top - fontMetrics.fTop, m_paintSurface);
    194 
    195     // draw the clear canvas button
    196     m_clearSurface.left = W - buttonWidth - 5;
    197     m_clearSurface.top = H - buttonHeight - 5;
    198     m_clearSurface.right = m_clearSurface.left + buttonWidth;
    199     m_clearSurface.bottom = m_clearSurface.top + buttonHeight;
    200     gCanvasI.drawRect(canvas, &m_clearSurface, m_paintButton);
    201     const char* clearText = "Clear";
    202     gCanvasI.drawText(canvas, clearText, strlen(clearText), m_clearSurface.left + 5,
    203                       m_clearSurface.top - fontMetrics.fTop, m_paintSurface);
    204 
    205     // draw the drawing surface box (5 px from the edge)
    206     m_drawingSurface.left = 5;
    207     m_drawingSurface.top = 5;
    208     m_drawingSurface.right = W - 5;
    209     m_drawingSurface.bottom = m_colorToggle.top - 5;
    210     gCanvasI.drawRect(canvas, &m_drawingSurface, m_paintSurface);
    211 
    212     // release the canvas
    213     releaseCanvas(canvas);
    214 }
    215 
    216 const char* PaintPlugin::getColorText() {
    217 
    218     if (m_activePaintColor == s_blueColor)
    219         return "Blue";
    220     else if (m_activePaintColor == s_greenColor)
    221         return "Green";
    222     else
    223         return "Red";
    224 }
    225 
    226 jobject PaintPlugin::getSurface() {
    227     if (m_surface) {
    228         return m_surface;
    229     }
    230 
    231     // load the appropriate java class and instantiate it
    232     JNIEnv* env = NULL;
    233     if (gVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    234         gLogI.log(kError_ANPLogType, " ---- getSurface: failed to get env");
    235         return NULL;
    236     }
    237 
    238     const char* className = "com.android.sampleplugin.PaintSurface";
    239     jclass paintClass = gSystemI.loadJavaClass(inst(), className);
    240 
    241     if(!paintClass) {
    242         gLogI.log(kError_ANPLogType, " ---- getSurface: failed to load class");
    243         return NULL;
    244     }
    245 
    246     PluginObject *obj = (PluginObject*) inst()->pdata;
    247     const int pW = obj->window->width;
    248     const int pH = obj->window->height;
    249 
    250     jmethodID constructor = env->GetMethodID(paintClass, "<init>", "(Landroid/content/Context;III)V");
    251     jobject paintSurface = env->NewObject(paintClass, constructor, m_context, (int)inst(), pW, pH);
    252 
    253     if(!paintSurface) {
    254         gLogI.log(kError_ANPLogType, " ---- getSurface: failed to construct object");
    255         return NULL;
    256     }
    257 
    258     m_surface = env->NewGlobalRef(paintSurface);
    259     return m_surface;
    260 }
    261 
    262 void PaintPlugin::destroySurface() {
    263     JNIEnv* env = NULL;
    264     if (m_surface && gVM->GetEnv((void**) &env, JNI_VERSION_1_4) == JNI_OK) {
    265 
    266         // detach the native code from the object
    267         jclass javaClass = env->GetObjectClass(m_surface);
    268         jmethodID invalMethod = env->GetMethodID(javaClass, "invalidateNPP", "()V");
    269         env->CallVoidMethod(m_surface, invalMethod);
    270 
    271         env->DeleteGlobalRef(m_surface);
    272         m_surface = NULL;
    273     }
    274 }
    275 
    276 int16_t PaintPlugin::handleEvent(const ANPEvent* evt) {
    277     switch (evt->eventType) {
    278         case kTouch_ANPEventType: {
    279             float x = (float) evt->data.touch.x;
    280             float y = (float) evt->data.touch.y;
    281             if (kDown_ANPTouchAction == evt->data.touch.action && m_isTouchCurrentInput) {
    282 
    283                 ANPRectF* rect = validTouch(evt->data.touch.x, evt->data.touch.y);
    284                 if(rect == &m_drawingSurface) {
    285                     m_isTouchActive = true;
    286                     gPathI.moveTo(m_touchPath, x, y);
    287                     paintTouch();
    288                     return 1;
    289                 }
    290 
    291             } else if (kMove_ANPTouchAction == evt->data.touch.action && m_isTouchActive) {
    292                 gPathI.lineTo(m_touchPath, x, y);
    293                 paintTouch();
    294                 return 1;
    295             } else if (kUp_ANPTouchAction == evt->data.touch.action && m_isTouchActive) {
    296                 gPathI.lineTo(m_touchPath, x, y);
    297                 paintTouch();
    298                 m_isTouchActive = false;
    299                 gPathI.reset(m_touchPath);
    300                 return 1;
    301             } else if (kCancel_ANPTouchAction == evt->data.touch.action) {
    302                 m_isTouchActive = false;
    303                 gPathI.reset(m_touchPath);
    304                 return 1;
    305             } else if (kDoubleTap_ANPTouchAction == evt->data.touch.action) {
    306                 gWindowI.requestCenterFitZoom(inst());
    307                 return 1;
    308 
    309             }
    310             break;
    311         }
    312         case kMouse_ANPEventType: {
    313 
    314             if (m_isTouchActive)
    315                 gLogI.log(kError_ANPLogType, "----%p Received unintended mouse event", inst());
    316 
    317             if (kDown_ANPMouseAction == evt->data.mouse.action) {
    318                 ANPRectF* rect = validTouch(evt->data.mouse.x, evt->data.mouse.y);
    319                 if (rect == &m_drawingSurface)
    320                     paintMouse(evt->data.mouse.x, evt->data.mouse.y);
    321                 else if (rect == &m_inputToggle)
    322                     toggleInputMethod();
    323                 else if (rect == &m_colorToggle)
    324                     togglePaintColor();
    325                 else if (rect == &m_fullScreenToggle)
    326                     gWindowI.requestFullScreen(inst());
    327                 else if (rect == &m_clearSurface)
    328                     drawCleanPlugin();
    329             }
    330             return 1;
    331         }
    332         case kCustom_ANPEventType: {
    333 
    334             switch (evt->data.other[0]) {
    335                 case kSurfaceCreated_CustomEvent:
    336                     gLogI.log(kDebug_ANPLogType, " ---- customEvent: surfaceCreated");
    337                     /* The second draw call is added to cover up a problem in this
    338                        plugin and is not a recommended usage pattern. This plugin
    339                        does not correctly make partial updates to the double
    340                        buffered surface and this second call hides that problem.
    341                      */
    342                     drawCleanPlugin();
    343                     drawCleanPlugin();
    344                     break;
    345                 case kSurfaceChanged_CustomEvent: {
    346                     gLogI.log(kDebug_ANPLogType, " ---- customEvent: surfaceChanged");
    347 
    348                     int width = evt->data.other[1];
    349                     int height = evt->data.other[2];
    350 
    351                     PluginObject *obj = (PluginObject*) inst()->pdata;
    352                     const int pW = obj->window->width;
    353                     const int pH = obj->window->height;
    354                     // compare to the plugin's surface dimensions
    355                     if (pW != width || pH != height)
    356                         gLogI.log(kError_ANPLogType,
    357                                   "----%p Invalid Surface Dimensions (%d,%d):(%d,%d)",
    358                                   inst(), pW, pH, width, height);
    359                     break;
    360                 }
    361                 case kSurfaceDestroyed_CustomEvent:
    362                     gLogI.log(kDebug_ANPLogType, " ---- customEvent: surfaceDestroyed");
    363                     break;
    364             }
    365             break; // end KCustom_ANPEventType
    366         }
    367         default:
    368             break;
    369     }
    370     return 0;   // unknown or unhandled event
    371 }
    372 
    373 ANPRectF* PaintPlugin::validTouch(int x, int y) {
    374 
    375     //convert to float
    376     float fx = (int) x;
    377     float fy = (int) y;
    378 
    379     if (fx > m_drawingSurface.left && fx < m_drawingSurface.right && fy > m_drawingSurface.top && fy < m_drawingSurface.bottom)
    380         return &m_drawingSurface;
    381     else if (fx > m_inputToggle.left && fx < m_inputToggle.right && fy > m_inputToggle.top && fy < m_inputToggle.bottom)
    382         return &m_inputToggle;
    383     else if (fx > m_colorToggle.left && fx < m_colorToggle.right && fy > m_colorToggle.top && fy < m_colorToggle.bottom)
    384         return &m_colorToggle;
    385     else if (fx > m_fullScreenToggle.left && fx < m_fullScreenToggle.right && fy > m_fullScreenToggle.top && fy < m_fullScreenToggle.bottom)
    386         return &m_fullScreenToggle;
    387     else if (fx > m_clearSurface.left && fx < m_clearSurface.right && fy > m_clearSurface.top && fy < m_clearSurface.bottom)
    388         return &m_clearSurface;
    389     else
    390         return NULL;
    391 }
    392 
    393 void PaintPlugin::toggleInputMethod() {
    394     m_isTouchCurrentInput = !m_isTouchCurrentInput;
    395 
    396     // lock only the input toggle and redraw the canvas
    397     ANPCanvas* lockedCanvas = getCanvas(&m_inputToggle);
    398     drawCleanPlugin(lockedCanvas);
    399 }
    400 
    401 void PaintPlugin::togglePaintColor() {
    402     if (m_activePaintColor == s_blueColor)
    403         m_activePaintColor = s_redColor;
    404     else if (m_activePaintColor == s_greenColor)
    405         m_activePaintColor = s_blueColor;
    406     else
    407         m_activePaintColor = s_greenColor;
    408 
    409     // lock only the color toggle and redraw the canvas
    410     ANPCanvas* lockedCanvas = getCanvas(&m_colorToggle);
    411     drawCleanPlugin(lockedCanvas);
    412 }
    413 
    414 void PaintPlugin::paintMouse(int x, int y) {
    415     //TODO do not paint outside the drawing surface
    416 
    417     //create the paint color
    418     ANPPaint* fillPaint = gPaintI.newPaint();
    419     gPaintI.setFlags(fillPaint, gPaintI.getFlags(fillPaint) | kAntiAlias_ANPPaintFlag);
    420     gPaintI.setStyle(fillPaint, kFill_ANPPaintStyle);
    421     gPaintI.setColor(fillPaint, m_activePaintColor);
    422 
    423     // handle the simple "mouse" paint (draw a point)
    424     ANPRectF point;
    425     point.left =   (float) x-3;
    426     point.top =    (float) y-3;
    427     point.right =  (float) x+3;
    428     point.bottom = (float) y+3;
    429 
    430     // get a canvas that is only locked around the point and draw it
    431     ANPCanvas* canvas = getCanvas(&point);
    432     gCanvasI.drawOval(canvas, &point, fillPaint);
    433 
    434     // clean up
    435     releaseCanvas(canvas);
    436     gPaintI.deletePaint(fillPaint);
    437 }
    438 
    439 void PaintPlugin::paintTouch() {
    440     //TODO do not paint outside the drawing surface
    441 
    442     //create the paint color
    443     ANPPaint* strokePaint = gPaintI.newPaint();
    444     gPaintI.setFlags(strokePaint, gPaintI.getFlags(strokePaint) | kAntiAlias_ANPPaintFlag);
    445     gPaintI.setColor(strokePaint, m_activePaintColor);
    446     gPaintI.setStyle(strokePaint, kStroke_ANPPaintStyle);
    447     gPaintI.setStrokeWidth(strokePaint, 6.0);
    448     gPaintI.setStrokeCap(strokePaint, kRound_ANPPaintCap);
    449     gPaintI.setStrokeJoin(strokePaint, kRound_ANPPaintJoin);
    450 
    451     // handle the complex "touch" paint (draw a line)
    452     ANPRectF bounds;
    453     gPathI.getBounds(m_touchPath, &bounds);
    454 
    455     // get a canvas that is only locked around the point and draw the path
    456     ANPCanvas* canvas = getCanvas(&bounds);
    457     gCanvasI.drawPath(canvas, m_touchPath, strokePaint);
    458 
    459     // clean up
    460     releaseCanvas(canvas);
    461     gPaintI.deletePaint(strokePaint);
    462 }
    463