home *** CD-ROM | disk | FTP | other *** search
/ Amiga Plus 2002 #3 / Amiga Plus CD - 2002 - No. 03.iso / AmigaPlus / Tools / Development / renderlib40 / src / rnd_palette.c < prev    next >
Encoding:
C/C++ Source or Header  |  2003-01-20  |  13.4 KB  |  681 lines

  1.  
  2. #include "lib_init.h"
  3. #include "lib_debug.h"
  4. #include <render/render.h>
  5. #include <proto/utility.h>
  6. #include <proto/exec.h>
  7.  
  8. /************************************************************************** 
  9. **
  10. **    createpalette
  11. */
  12.  
  13. LIBAPI RNDPAL *CreatePaletteA(struct TagItem *tags)
  14. {
  15.     APTR rmh;
  16.     RNDPAL *p;
  17.     UWORD type;
  18.     
  19.     type = (UWORD) GetTagData(RND_HSType, HSTYPE_15BIT, tags);
  20.  
  21.     type &= HSTYPE_MASK;
  22.     switch (type)
  23.     {
  24.         case HSTYPE_12BIT:
  25.         case HSTYPE_15BIT:
  26.         case HSTYPE_18BIT:
  27.             break;
  28.  
  29.         default:
  30.             return NULL;
  31.     }
  32.  
  33.     rmh = (APTR) GetTagData(RND_RMHandler, NULL, tags);
  34.     p = AllocRenderVecClear(rmh, sizeof(RNDPAL));
  35.     if (p)
  36.     {
  37.         InitSemaphore(&p->lock);
  38.         p->type = type;
  39.         p->rmh = rmh;
  40.         p->numcolors = 0;
  41.         p->p2mask = 0xff >> (8 - type);                /* ...11111 */
  42.         p->p2mask2 = p->p2mask << (8 - type);        /* 11111... */
  43.     }
  44.     return p;
  45. }
  46.  
  47. /************************************************************************** 
  48. **
  49. **    deletepalette
  50. */
  51.  
  52. LIBAPI void DeletePalette(RNDPAL *palette)
  53. {
  54.     FlushPalette(palette);
  55.     FreeRenderVec((ULONG *) palette);
  56. }
  57.  
  58. /************************************************************************** 
  59. **
  60. **    importpalette
  61. */
  62.  
  63. LIBAPI void ImportPaletteA(RNDPAL *palette, APTR coltab, UWORD numcol, struct TagItem *tags)
  64. {
  65.     BOOL newpal = GetTagData(RND_NewPalette, TRUE, tags);
  66.     BOOL ehbpal = GetTagData(RND_EHBPalette, FALSE, tags);
  67.     ULONG palfmt = GetTagData(RND_PaletteFormat, PALFMT_RGB8, tags);
  68.     ULONG firstcol = GetTagData(RND_FirstColor, 0, tags);
  69.     ULONG *pptr, *sptr;
  70.     ULONG i, rgb;
  71.     
  72.     switch (palfmt)
  73.     {
  74.         case PALFMT_RGB32:
  75.         case PALFMT_RGB8:
  76.         case PALFMT_RGB4:
  77.         case PALFMT_PALETTE:
  78.             if (palette && numcol && coltab) break;
  79.         default:
  80.             return;
  81.     }
  82.  
  83.     ObtainSemaphore(&palette->lock);
  84.     pptr = palette->table;
  85.     
  86.     if (newpal)
  87.     {
  88.         palette->numcolors = 0;
  89.         memfill32(pptr, 256*4, 0);
  90.     }
  91.     
  92.     if (palfmt == PALFMT_PALETTE)
  93.     {
  94.         ObtainSemaphoreShared(&((RNDPAL *) coltab)->lock);
  95.         sptr = ((RNDPAL *) coltab)->table;
  96.     }
  97.     else
  98.     {
  99.         sptr = coltab;
  100.     }
  101.  
  102.     for (i = firstcol; i < firstcol + numcol; ++i)
  103.     {
  104.         switch (palfmt)
  105.         {
  106.             case PALFMT_RGB32:
  107.                 rgb = (*sptr++ & 0xff000000) >> 8;
  108.                 rgb |= (*sptr++ & 0xff000000) >> 16;
  109.                 rgb |= (*sptr++ & 0xff000000) >> 24;
  110.                 pptr[i] = rgb;
  111.                 break;
  112.                 
  113.             case PALFMT_PALETTE:
  114.             case PALFMT_RGB8:
  115.                 pptr[i] = *sptr++;
  116.                 break;
  117.             
  118.             case PALFMT_RGB4:
  119.             {
  120.                 UWORD *sp = (UWORD *) sptr;
  121.                 rgb = (ULONG) *sp++;
  122.                 pptr[i] = ((rgb & 0xf00) << 12) |
  123.                             ((rgb & 0xff0) << 8) |
  124.                             ((rgb & 0x0ff) << 4) |
  125.                             (rgb & 0x00f);
  126.                 sptr = (ULONG *) sp;
  127.                 break;
  128.             }
  129.         }
  130.         
  131.         if (ehbpal && i >= 0 && i < 32)
  132.         {
  133.             pptr[i + 32] = (pptr[i] >> 1) & 0x7f7f7f;
  134.         }
  135.     }
  136.  
  137.     if (palfmt == PALFMT_PALETTE)
  138.     {
  139.         ReleaseSemaphore(&((RNDPAL *) coltab)->lock);
  140.     }
  141.     
  142.     if (firstcol + numcol > palette->numcolors)
  143.     {
  144.         palette->numcolors = firstcol + numcol;
  145.     }
  146.     
  147.     FlushPalette(palette);
  148.     
  149.     ReleaseSemaphore(&palette->lock);
  150. }
  151.  
  152.  
  153. /************************************************************************** 
  154. **
  155. **    exportpalette
  156. */
  157.  
  158. LIBAPI void ExportPaletteA(RNDPAL *palette, ULONG *coltab, struct TagItem *tags)
  159. {
  160.     ULONG palfmt = GetTagData(RND_PaletteFormat, PALFMT_RGB8, tags);
  161.     ULONG firstcol = GetTagData(RND_FirstColor, 0, tags);
  162.     ULONG numcol, i, rgb;
  163.  
  164.     switch (palfmt)
  165.     {
  166.         case PALFMT_RGB32:
  167.         case PALFMT_RGB8:
  168.         case PALFMT_RGB4:
  169.             if (palette && coltab) break;
  170.         default:
  171.             return;
  172.     }
  173.  
  174.     numcol = GetTagData(RND_NumColors, palette->numcolors, tags);
  175.     if (!numcol) return;
  176.     
  177.     ObtainSemaphoreShared(&palette->lock);
  178.     
  179.     for (i = firstcol; i < firstcol + numcol; ++i)
  180.     {
  181.         rgb = palette->table[i];
  182.         switch (palfmt)
  183.         {
  184.             case PALFMT_RGB32:
  185.                 *coltab++ = ((rgb & 0xff0000) << 8) | (rgb & 0xff0000) |
  186.                     ((rgb & 0xff0000) >> 8) | ((rgb & 0xff0000) >> 16);
  187.                 *coltab++ = ((rgb & 0x00ff00) << 16) | ((rgb & 0x00ff00) << 8) |
  188.                     (rgb & 0x00ff00) | ((rgb & 0x00ff00) >> 8);
  189.                 *coltab++ = ((rgb & 0x0000ff) << 24) | ((rgb & 0x0000ff) << 16) |
  190.                     ((rgb & 0x0000ff) << 8) | (rgb & 0x0000ff);
  191.                 break;
  192.                 
  193.             case PALFMT_RGB8:
  194.                 *coltab++ = rgb;
  195.                 break;
  196.                 
  197.             case PALFMT_RGB4:
  198.             {
  199.                 UWORD *dp = (UWORD *) coltab;
  200.                 *dp++ = ((rgb & 0xf00000) >> 12) | ((rgb & 0xf000) >> 8) | ((rgb & 0xf0) >> 4);
  201.                 coltab = (ULONG *) dp;
  202.             }
  203.         }
  204.     }
  205.  
  206.     ReleaseSemaphore(&palette->lock);
  207. }
  208.  
  209.  
  210. /************************************************************************** 
  211. **
  212. **    flushpalette
  213. */
  214.  
  215. LIBAPI void FlushPalette(RNDPAL *palette)
  216. {
  217.     if (palette)
  218.     {
  219.         ObtainSemaphore(&palette->lock);
  220.         FreeRenderVec((ULONG *) palette->p2table);
  221.         palette->p2table = NULL;
  222.         ReleaseSemaphore(&palette->lock);
  223.     }
  224. }
  225.  
  226.  
  227. /************************************************************************** 
  228. **
  229. **    calcpen
  230. */
  231.  
  232. static WORD calcpen(RNDPAL *p, ULONG rgb)
  233. {
  234.     LONG i, d;
  235.     WORD besti;
  236.     LONG bestd = 196000;
  237.     WORD r, g, b;
  238.     WORD dr, dg, db;
  239.     
  240.     r = (rgb & 0xff0000) >> 16;
  241.     g = (rgb & 0x00ff00) >> 8;
  242.     b = (rgb & 0x0000ff);
  243.  
  244.     for (i = 0; i < p->numcolors; ++i)
  245.     {
  246.         rgb = p->table[i];
  247.         dr = r - ((rgb & 0xff0000) >> 16);
  248.         dg = g - ((rgb & 0x00ff00) >> 8);
  249.         db = b - (rgb & 0x0000ff);
  250.         d = dr*dr + dg*dg + db*db;
  251.         if (d < bestd)
  252.         {
  253.             besti = i;
  254.             bestd = d;
  255.             if (bestd == 0) break;
  256.         }
  257.     }
  258.     return besti;
  259. }
  260.  
  261. static WORD calcpen2(RNDPAL *p, WORD r, WORD g, WORD b)
  262. {
  263.     LONG i, d;
  264.     WORD besti;
  265.     LONG bestd = 196000;
  266.     WORD dr, dg, db;
  267.     ULONG rgb;
  268.     
  269.     for (i = 0; i < p->numcolors; ++i)
  270.     {
  271.         rgb = p->table[i];
  272.         dr = r - ((rgb & 0xff0000) >> 16);
  273.         dg = g - ((rgb & 0x00ff00) >> 8);
  274.         db = b - (rgb & 0x0000ff);
  275.         d = dr*dr + dg*dg + db*db;
  276.         if (d < bestd)
  277.         {
  278.             besti = i;
  279.             bestd = d;
  280.             if (bestd == 0) break;
  281.         }
  282.     }
  283.     return besti;
  284. }
  285.  
  286.  
  287. /************************************************************************** 
  288. **
  289. **    p2table
  290. */
  291.  
  292. LIBAPI WORD *GetP2Table(RNDPAL *p)
  293. {
  294.     if (!p->p2table)
  295.     {
  296.         ULONG size = (1L << (3 * p->type)) * sizeof(UWORD);
  297.         p->p2table = AllocRenderVec(p->rmh, size);
  298.         if (p->p2table)
  299.         {
  300.             memfill32((ULONG *) p->p2table, size, 0xffffffff);
  301.         }
  302.     }
  303.     return p->p2table;
  304. }
  305.  
  306. static __inline ULONG P2Index(RNDPAL *pal, ULONG rgb)
  307. {
  308.     ULONG foo;
  309.     rgb >>= 8 - pal->type;
  310.     foo = (rgb & pal->p2mask);
  311.     foo <<= pal->type;
  312.     rgb >>= 8;
  313.     foo |= (rgb & pal->p2mask);
  314.     foo <<= pal->type;
  315.     rgb >>= 8;
  316.     foo |= rgb;
  317.     return foo;
  318. }
  319.  
  320. LIBAPI __inline WORD P2Lookup(RNDPAL *pal, ULONG rgb)
  321. {
  322.     WORD *p2tab = pal->p2table + P2Index(pal, rgb);
  323.     WORD result = *p2tab;
  324.     if (result < 0)
  325.     {
  326.         result = calcpen(pal, rgb);
  327.         *p2tab = result;
  328.     }
  329.     return result;
  330. }
  331.  
  332.  
  333. static __inline ULONG P2Index2(RNDPAL *pal, WORD r, WORD g, WORD b)
  334. {
  335.     ULONG foo;
  336.     foo = b & pal->p2mask2;
  337.     foo <<= pal->type;
  338.     foo |= g & pal->p2mask2;
  339.     foo <<= pal->type;
  340.     foo |= r & pal->p2mask2;
  341.     foo >>= 8 - pal->type;
  342.     return foo;
  343. }
  344.  
  345. LIBAPI __inline WORD P2Lookup2(RNDPAL *pal, WORD r, WORD g, WORD b)
  346. {
  347.     WORD *p2tab = pal->p2table + P2Index2(pal, r, g, b);
  348.     WORD result = *p2tab;
  349.     if (result < 0)
  350.     {
  351.         result = calcpen2(pal, r, g, b);
  352.         *p2tab = result;
  353.     }
  354.     return result;
  355. }
  356.  
  357.  
  358. /************************************************************************** 
  359. **
  360. **    bestpen
  361. */
  362.  
  363. LIBAPI LONG BestPen(RNDPAL *p, ULONG rgb)
  364. {
  365.     LONG result = -1;
  366.     if (p)
  367.     {
  368.         ObtainSemaphore(&p->lock);
  369.         if (GetP2Table(p))
  370.         {
  371.             result = P2Lookup(p, rgb);
  372.         }
  373.         else
  374.         {
  375.             result = calcpen(p, rgb);
  376.         }
  377.         ReleaseSemaphore(&p->lock);
  378.     }
  379.     return result;
  380. }
  381.  
  382.  
  383. /************************************************************************** 
  384. **
  385. **    getpaletteattrs
  386. */
  387.  
  388. LIBAPI ULONG GetPaletteAttrs(RNDPAL *p, ULONG args)
  389. {
  390.     return p->numcolors;
  391. }
  392.  
  393.  
  394.  
  395. /**************************************************************************
  396. **
  397. **    success = heapsort(data, refarray, length, cmpfunc)
  398. **        LONG (*cmpfunc)(APTR data, APTR ref1, APTR ref2)
  399. */
  400.  
  401. typedef LONG (*CMPFUNC)(APTR userdata, APTR ref1, APTR ref2);
  402.  
  403. static BOOL heapsort(APTR data, APTR *refarray, ULONG length, CMPFUNC cmpfunc)
  404. {
  405.     ULONG indx, k, j, half, limit;
  406.     APTR temp;
  407.     
  408.     if (refarray && cmpfunc && length > 1)
  409.     {
  410.         indx = (length >> 1) - 1;
  411.         do
  412.         {
  413.             k = indx;
  414.             temp = refarray[k];
  415.             limit = length - 1;
  416.             half = length >> 1;
  417.             while (k < half)
  418.             {
  419.                 j = k + k + 1;
  420.                 if ((j < limit) && ((*cmpfunc)(data, refarray[j + 1], refarray[j]) > 0))
  421.                 {
  422.                     ++j;
  423.                 }
  424.                 if ((*cmpfunc)(data, temp, refarray[j]) >= 0)
  425.                 {
  426.                     break;
  427.                 }
  428.                 refarray[k] = refarray[j];
  429.                 k = j;
  430.             }
  431.             refarray[k] = temp;
  432.         } while (indx-- != 0);
  433.     
  434.         while (--length > 0)
  435.         {
  436.             temp = refarray[0];
  437.             refarray[0] = refarray[length];
  438.             refarray[length] = temp;
  439.             k = 0;
  440.             temp = refarray[k];
  441.             limit = length - 1;
  442.             half = length >> 1;
  443.             while (k < half)
  444.             {
  445.                 j = k + k + 1;
  446.                 if ((j < limit) && ((*cmpfunc)(data, refarray[j + 1], refarray[j]) > 0))
  447.                 {
  448.                     ++j;
  449.                 }
  450.                 if ((*cmpfunc)(data, temp, refarray[j]) >= 0)
  451.                 {
  452.                     break;
  453.                 }
  454.                 refarray[k] = refarray[j];
  455.                 k = j;
  456.             }
  457.             refarray[k] = temp;
  458.         }
  459.         return TRUE;
  460.     }
  461.     return FALSE;
  462. }
  463.  
  464.  
  465. /************************************************************************** 
  466. **
  467. **    sortpalette
  468. */
  469.  
  470. static LONG cmplum(ULONG rgb1, ULONG rgb2)
  471. {
  472.     ULONG Y1 =     0.299 * ((rgb1 & 0xff0000) >> 16) +
  473.                 0.587 * ((rgb1 & 0x00ff00) >> 8) +
  474.                 0.114 * (rgb1 & 0x0000ff);
  475.     ULONG Y2 =     0.299 * ((rgb2 & 0xff0000) >> 16) +
  476.                 0.587 * ((rgb2 & 0x00ff00) >> 8) +
  477.                 0.114 * (rgb2 & 0x0000ff);
  478.  
  479.     if (Y1 > Y2) return 1;
  480.     if (Y1 == Y2) return 0;
  481.     return -1;
  482. }
  483.  
  484. static LONG cmpfunc_lum_desc(APTR data, APTR rgb1, APTR rgb2)
  485. {
  486.     return -cmplum((ULONG) rgb1, (ULONG) rgb2);
  487. }
  488. static LONG cmpfunc_lum_asc(APTR data, APTR rgb1, APTR rgb2)
  489. {
  490.     return cmplum((ULONG) rgb1, (ULONG) rgb2);
  491. }
  492.  
  493.  
  494. static LONG cmpsat(ULONG rgb1, ULONG rgb2)
  495. {
  496.     ULONG S1, S2;
  497.     LONG r = ((rgb1 & 0xff0000) >> 16);
  498.     LONG g = ((rgb1 & 0x00ff00) >> 8);
  499.     LONG b = (rgb1 & 0x0000ff);
  500.     S1 = (r-g)*(r-g);
  501.     S1 += (g-b)*(g-b);
  502.     S1 += (b-r)*(b-r);
  503.     r = ((rgb2 & 0xff0000) >> 16);
  504.     g = ((rgb2 & 0x00ff00) >> 8);
  505.     b = (rgb2 & 0x0000ff);
  506.     S2 = (r-g)*(r-g);
  507.     S2 += (g-b)*(g-b);
  508.     S2 += (b-r)*(b-r);
  509.     if (S1 > S2) return 1;
  510.     if (S1 == S2) return 0;
  511.     return -1;
  512. }
  513.  
  514. static LONG cmpfunc_sat_desc(APTR data, APTR rgb1, APTR rgb2)
  515. {
  516.     return -cmpsat((ULONG) rgb1, (ULONG) rgb2);
  517. }
  518. static LONG cmpfunc_sat_asc(APTR data, APTR rgb1, APTR rgb2)
  519. {
  520.     return cmpsat((ULONG) rgb1, (ULONG) rgb2);
  521. }
  522.  
  523.  
  524.  
  525. struct SortHistoEntry
  526. {
  527.     FLOAT measure;
  528.     ULONG rgb;
  529. };
  530.  
  531. static LONG cmpfunc_histo_desc(APTR data, struct SortHistoEntry *ref1, struct SortHistoEntry *ref2)
  532. {
  533.     if (ref1->measure > ref2->measure) return -1;
  534.     if (ref1->measure == ref2->measure) return 0;
  535.     return 1;
  536. }
  537.  
  538. static LONG cmpfunc_histo_asc(APTR data, struct SortHistoEntry *ref1, struct SortHistoEntry *ref2)
  539. {
  540.     if (ref1->measure > ref2->measure) return 1;
  541.     if (ref1->measure == ref2->measure) return 0;
  542.     return -1;
  543. }
  544.  
  545.  
  546. /* 
  547. **    adapthistogram(h, palette)
  548. **    adapt histogram to palette, calculate entries in
  549. **    sorthisto table
  550. */
  551.  
  552. static void adapthistogram(struct RNDTreeNode *node, RNDPAL *p, struct SortHistoEntry *table, ULONG sortmode)
  553. {
  554.     UBYTE pen;
  555.     if (node->left)
  556.     {
  557.         adapthistogram(node->left, p, table, sortmode);
  558.     }
  559.  
  560.     if (node->right)
  561.     {
  562.         adapthistogram(node->right, p, table, sortmode);
  563.     }
  564.     
  565.     pen = P2Lookup(p, node->entry.rgb);
  566.     switch (sortmode)
  567.     {
  568.         case PALMODE_SIGNIFICANCE:
  569.         case PALMODE_POPULARITY:
  570.             table[pen].measure += node->entry.count;
  571.             break;
  572.         case PALMODE_REPRESENTATION:
  573.             table[pen].measure++;
  574.             break;
  575.     }
  576. }
  577.  
  578.  
  579. LIBAPI ULONG SortPaletteA(RNDPAL *p, ULONG mode, struct TagItem *tags)
  580. {
  581.     ULONG result = SORTP_NO_DATA;
  582.     if (p)
  583.     {
  584.         APTR sortdata = p->table;
  585.         struct SortHistoEntry *table = NULL;
  586.         struct SortHistoEntry **reftable = NULL;
  587.         LONG (*cmpfunc)(APTR, APTR, APTR) = NULL;
  588.         RNDHISTO *h = (RNDHISTO *) GetTagData(RND_Histogram, NULL, tags);
  589.         LONG i;
  590.  
  591.         ObtainSemaphore(&p->lock);
  592.         
  593.         switch (mode)
  594.         {
  595.             case PALMODE_BRIGHTNESS:
  596.                 cmpfunc = cmpfunc_lum_desc;
  597.                 break;
  598.             case PALMODE_BRIGHTNESS | PALMODE_ASCENDING:
  599.                 cmpfunc = cmpfunc_lum_asc;
  600.                 break;
  601.             case PALMODE_SATURATION:
  602.                 cmpfunc = cmpfunc_sat_desc;
  603.                 break;
  604.             case PALMODE_SATURATION | PALMODE_ASCENDING:
  605.                 cmpfunc = cmpfunc_sat_asc;
  606.                 break;
  607.  
  608.             case PALMODE_SIGNIFICANCE | PALMODE_ASCENDING:
  609.             case PALMODE_REPRESENTATION | PALMODE_ASCENDING:
  610.             case PALMODE_POPULARITY | PALMODE_ASCENDING:
  611.                 cmpfunc = (CMPFUNC) cmpfunc_histo_asc;
  612.  
  613.             case PALMODE_SIGNIFICANCE:
  614.             case PALMODE_REPRESENTATION:
  615.             case PALMODE_POPULARITY:
  616.                 if (!cmpfunc) cmpfunc = (CMPFUNC) cmpfunc_histo_desc;
  617.                 if (!h) goto fail;
  618.                 result = SORTP_NOT_ENOUGH_MEMORY;
  619.                 if (!GetP2Table(p)) goto fail;
  620.                 table = AllocRenderVec(p->rmh, sizeof(struct SortHistoEntry) * p->numcolors);
  621.                 reftable = AllocRenderVec(p->rmh, sizeof(ULONG) * p->numcolors);
  622.                 if (!table || !reftable) goto fail;
  623.                 
  624.                 for (i = 0; i < p->numcolors; ++i)
  625.                 {
  626.                     table[i].measure = 0;
  627.                     table[i].rgb = p->table[i];
  628.                     reftable[i] = &table[i];
  629.                 }
  630.  
  631.                 sortdata = reftable;
  632.  
  633.                 ObtainSemaphore(&h->lock);
  634.                 adapthistogram(h->root, p, table, mode & 7);
  635.                 ReleaseSemaphore(&h->lock);
  636.  
  637.                 if ((mode & 7) == PALMODE_SIGNIFICANCE)
  638.                 {
  639.                     LONG r, g, b, S;
  640.                     for (i = 0; i < p->numcolors; ++i)
  641.                     {
  642.                         r = (table[i].rgb & 0xff0000) >> 16;
  643.                         g = (table[i].rgb & 0x00ff00) >> 8;
  644.                         b = (table[i].rgb & 0x0000ff);
  645.                         S = (r-g)*(r-g);
  646.                         S += (g-b)*(g-b);
  647.                         S += (b-r)*(b-r);
  648.                         table[i].measure *= S;
  649.                     }
  650.                 }
  651.                 break;
  652.  
  653.             default:
  654.                 result = SORTP_NOT_IMPLEMENTED;
  655.                 goto fail;
  656.         }
  657.         
  658.         heapsort(NULL, sortdata, p->numcolors, cmpfunc);
  659.  
  660.         if (table)
  661.         {
  662.             for (i = 0; i < p->numcolors; ++i)
  663.             {
  664.                 p->table[i] = reftable[i]->rgb;
  665.             }
  666.         }
  667.  
  668.         FlushPalette(p);
  669.  
  670.         result = SORTP_SUCCESS;
  671.  
  672. fail:    if (table) FreeRenderVec((ULONG *) table);
  673.         if (reftable) FreeRenderVec((ULONG *) reftable);
  674.  
  675.         ReleaseSemaphore(&p->lock);
  676.     }
  677.     
  678.     return result;
  679. }
  680.  
  681.