The OpenD Programming Language

1 /**
2 Manipulation on image types and layout, that do not belong to public API.
3 
4 Copyright: Copyright Guillaume Piolat 2022
5 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 
7 */
8 module gamut.internals.types;
9 
10 //import core.stdc.stdlib: malloc, realloc, free;
11 import core.stdc.string: memset;
12 
13 import gamut.types;
14 
15 nothrow @nogc @safe:
16 
17 enum LayoutConstraints
18     LAYOUT_MULTIPLICITY_MASK     = 3,
19     LAYOUT_TRAILING_MASK         = 12,
20     LAYOUT_BORDER_MASK           = 384,
21     LAYOUT_SCANLINE_ALIGNED_MASK = 112;
22 
23 
24 /// Returns: `true` if this `PixelType` is "plain", meaning that it's 1/2/3/4 channel of L/LA/RGB/RGBA data.
25 /// Currently: all images are plain, or have no data.
26 bool pixelTypeIsPlain(PixelType t) pure
27 {
28     return true;
29 }
30 
31 /// Returns: `true` if this `PixelType` is planar, completely unsupported for now.
32 bool pixelTypeIsPlanar(PixelType t) pure
33 {
34     return false; // No support yet in gamut.
35 }
36 
37 /// Returns: `true` if this `PixelType` is compressed, meaning the data is inscrutable until decoded.
38 bool pixelTypeIsCompressed(PixelType t) pure
39 {
40     return false; // No support yet in gamut.
41 }
42 
43 /// Size of one pixel for given pixel type `type`, in bytes.
44 int pixelTypeSize(PixelType type) pure
45 {
46     final switch(type) with (PixelType)
47     {
48         case l8:      return 1;
49         case l16:     return 2;
50         case lf32:    return 4;
51         case la8:     return 2;
52         case la16:    return 4;
53         case laf32:   return 8;
54         case rgb8:    return 3;
55         case rgb16:   return 6;
56         case rgba8:   return 4;
57         case rgba16:  return 8;
58         case rgbf32:  return 12;
59         case rgbaf32: return 16;
60         case unknown: assert(false);
61     }
62 }
63 
64 enum int GAMUT_MAX_PIXEL_SIZE = 16; // keep it in sync
65 
66 /// Number of channels in this image type.
67 int pixelTypeNumChannels(PixelType type) pure
68 {
69     final switch(type) with (PixelType)
70     {
71         case l8:      return 1;
72         case l16:     return 1;
73         case lf32:    return 1;
74         case la8:     return 2;
75         case la16:    return 2;
76         case laf32:   return 2;
77         case rgb8:    return 3;
78         case rgb16:   return 3;
79         case rgbf32:  return 3;
80         case rgba8:   return 4;
81         case rgba16:  return 4;
82         case rgbaf32: return 4;
83         case unknown: assert(false);
84     }
85 }
86 
87 /// Is this type 8-bit?
88 bool pixelTypeIs8Bit(PixelType type) pure
89 {
90     switch(type) with (PixelType)
91     {
92         case l8:
93         case la8:
94         case rgb8:
95         case rgba8:
96             return true;
97         default:
98             return false;
99     }
100 }
101 
102 /// Is this type 16-bit?
103 bool pixelTypeIs16Bit(PixelType type) pure
104 {
105     switch(type) with (PixelType)
106     {
107         case l16:
108         case la16:
109         case rgb8:
110         case rgba8:
111             return true;
112         default:
113             return false;
114     }
115 }
116 
117 /// Is this type 32-bit floating-point?
118 int pixelTypeIsFP32(PixelType type) pure
119 {
120     switch(type) with (PixelType)
121     {
122         case lf32:
123         case laf32:
124         case rgbf32:
125         case rgbaf32:
126             return true;
127         default:
128             return false;
129     }
130 }
131 
132 /// Can this pixel type be losslessly expressed in rgba8?
133 bool pixelTypeExpressibleInRGBA8(PixelType type) pure
134 {
135     return pixelTypeIs8Bit(type);
136 }
137 
138 /// Check if these image dimensions are valid in Gamut.
139 bool imageIsValidSize(int layers, int width, int height) pure
140 {
141     if (layers < 0 || width < 0 || height < 0)
142         return false;
143 
144     if (layers > GAMUT_MAX_IMAGE_LAYERS)
145         return false;
146 
147     if (width > GAMUT_MAX_IMAGE_WIDTH || height > GAMUT_MAX_IMAGE_HEIGHT)
148         return false;
149 
150     return true;
151 }
152 
153 
154 /// From a layout constraint, get requested pixel multiplicity.
155 int layoutMultiplicity(LayoutConstraints constraints) pure
156 {
157     return 1 << (constraints & 3);
158 }
159 unittest
160 {
161     assert(layoutMultiplicity(LAYOUT_MULTIPLICITY_1) == 1);
162     assert(layoutMultiplicity(LAYOUT_MULTIPLICITY_8) == 8);
163 }
164 
165 /// From a layout constraint, get requested trailing pixels.
166 int layoutTrailingPixels(LayoutConstraints constraints) pure @trusted
167 {
168     return (1 << ((constraints & 0x0C) >> 2)) - 1;
169 }
170 unittest
171 {
172     assert(layoutTrailingPixels(LAYOUT_TRAILING_0) == 0);
173     assert(layoutTrailingPixels(LAYOUT_TRAILING_1) == 1);
174     assert(layoutTrailingPixels(LAYOUT_TRAILING_3) == 3);
175     assert(layoutTrailingPixels(LAYOUT_TRAILING_7 | LAYOUT_MULTIPLICITY_8) == 7);
176 }
177 
178 /// From a layout constraint, get scanline alignment.
179 int layoutScanlineAlignment(LayoutConstraints constraints) pure
180 {
181     return 1 << ((constraints >> 4) & 0x0f);
182 }
183 unittest
184 {
185     assert(layoutScanlineAlignment(LAYOUT_SCANLINE_ALIGNED_1 | LAYOUT_TRAILING_7) == 1);
186     assert(layoutScanlineAlignment(LAYOUT_SCANLINE_ALIGNED_128) == 128);
187 }
188 
189 
190 /// What is the scanline alignement of such a pointer?
191 LayoutConstraints getPointerAlignment(size_t ptr)
192 {
193     if ( (ptr & 127) == 0) return LAYOUT_SCANLINE_ALIGNED_128;
194     if ( (ptr & 63) == 0) return LAYOUT_SCANLINE_ALIGNED_64;
195     if ( (ptr & 31) == 0) return LAYOUT_SCANLINE_ALIGNED_32;
196     if ( (ptr & 15) == 0) return LAYOUT_SCANLINE_ALIGNED_16;
197     if ( (ptr & 7) == 0) return LAYOUT_SCANLINE_ALIGNED_8;
198     if ( (ptr & 3) == 0) return LAYOUT_SCANLINE_ALIGNED_4;
199     if ( (ptr & 1) == 0) return LAYOUT_SCANLINE_ALIGNED_2;
200     return LAYOUT_SCANLINE_ALIGNED_1;
201 }
202 
203 /// From a layout constraint, get surrounding border width.
204 int layoutBorderWidth(LayoutConstraints constraints) pure
205 {
206     return (constraints >> 7) & 3;
207 }
208 unittest
209 {
210     assert(layoutBorderWidth(LAYOUT_BORDER_0) == 0);
211     assert(layoutBorderWidth(LAYOUT_BORDER_1) == 1);
212     assert(layoutBorderWidth(LAYOUT_BORDER_2 | LAYOUT_TRAILING_7) == 2);
213     assert(layoutBorderWidth(LAYOUT_BORDER_3) == 3);
214 }
215 
216 /// From a layout constraint, get if being gapless is guaranteed.
217 bool layoutGapless(LayoutConstraints constraints) pure
218 {
219     return (constraints & LAYOUT_GAPLESS) != 0;
220 }
221 unittest
222 {
223     assert(layoutGapless(LAYOUT_GAPLESS));
224     assert(!layoutGapless(0));
225 }
226 
227 /// Assuming the same `PixelType`, can an allocation made with constraint `older` 
228 /// be used with constraint `newer`?
229 /// Note: `older` doesn't need to be a valid LayoutConstraints, but newer must be. 
230 bool layoutConstraintsCompatible(LayoutConstraints newer, LayoutConstraints older) pure
231 {
232     if ((newer & LAYOUT_GAPLESS) && !(older & LAYOUT_GAPLESS))
233         return false;
234 
235     if ((newer & LAYOUT_VERT_FLIPPED) && !(older & LAYOUT_VERT_FLIPPED))
236         return false;
237     if ((newer & LAYOUT_VERT_STRAIGHT) && !(older & LAYOUT_VERT_STRAIGHT))
238         return false;
239 
240     if (layoutMultiplicity(newer) > layoutMultiplicity(older))
241         return false;
242 
243     if (layoutTrailingPixels(newer) > layoutTrailingPixels(older))
244         return false;
245 
246     if (layoutScanlineAlignment(newer) > layoutScanlineAlignment(older))
247         return false;
248 
249     if (layoutBorderWidth(newer) > layoutBorderWidth(older))
250         return false;
251 
252     return true; // is compatible
253 }
254 
255 /// _Assuming the same `PixelType`, can an allocation made with constraint `older` 
256 /// be used with constraint `newer`?
257 bool layoutConstraintsValid(LayoutConstraints constraints) pure
258 {
259     bool forceVFlipped    = (constraints & LAYOUT_VERT_FLIPPED) != 0;
260     bool forceNonVFlipped = (constraints & LAYOUT_VERT_STRAIGHT) != 0;
261 
262     if (forceVFlipped && forceNonVFlipped)
263         return false; // Can't be flipped and non-flipped at the same time.
264 
265     // LAYOUT_GAPLESS is incompatible with almost anything
266     if (layoutGapless(constraints))
267     {
268         if (layoutMultiplicity(constraints) > 1)
269             return false;
270         if (layoutTrailingPixels(constraints) > 0)
271             return false;
272         if (layoutScanlineAlignment(constraints) > 1)
273             return false;
274         if (layoutBorderWidth(constraints) > 0)
275             return false;
276     }
277 
278     return true; // Those constraints are not exclusive.
279 }
280 
281 
282 // As input: first scanline pointer and pitch in byte.
283 // As output: same, but following the constraints (flipped optionally).
284 // This is a way to flip image vertically.
285 void flipScanlinePointers(int width,
286                           int height,
287                           ref ubyte* dataPointer, 
288                           ref int bytePitch) pure @system
289 {
290     if (height >= 2) // Nothing to do for 0 or 1 row
291     {
292         ptrdiff_t offset_to_Nm1_row = cast(ptrdiff_t)(bytePitch) * (height - 1);
293         dataPointer += offset_to_Nm1_row;
294     }
295     bytePitch = -bytePitch;
296 }
297 
298 // As input: first scanline pointer and pitch in byte.
299 // As output: same, but following the constraints (flipped optionally).
300 void applyVFlipConstraintsToScanlinePointers(int width,
301                                              int height,
302                                              ref ubyte* dataPointer, 
303                                              ref int bytePitch,
304                                              LayoutConstraints constraints) pure @system
305 {
306     assert(layoutConstraintsValid(constraints));
307 
308     bool forceVFlipStorage    = (constraints & LAYOUT_VERT_FLIPPED) != 0;
309     bool forceNonVFlipStorage = (constraints & LAYOUT_VERT_STRAIGHT) != 0;
310 
311     // Should we flip the first scanline pointer and pitch?
312     bool shouldFlip = ( forceVFlipStorage && bytePitch > 0) || (forceNonVFlipStorage && bytePitch < 0);
313 
314     if (shouldFlip)
315     {
316         flipScanlinePointers(width, height, dataPointer, bytePitch);      
317     }
318 }
319 
320 /// Allocate pixel data. Discard ancient data if any, and reallocate with `realloc`.
321 ///
322 /// Returns true in `err` in case of success. If the function is successful 
323 /// then `deallocatePixelStorage` MUST be called later on.
324 ///
325 /// Params:
326 ///     existingData The existing `mallocArea` from a former call to `allocatePixelStorage`.
327 ///     type         Pixel data type.
328 ///     layers       How many such of image are allocated. Must be 1 or greater.
329 ///     width        Image width.
330 ///     height       Image height.
331 ///     constraints  The layout constraints to follow for the scanlines and allocation. MUST be valid.
332 ///     bonusBytes   If non-zero, the area mallocArea[0..bonusBytes] can be used for user storage.
333 ///                  Only the caller can use as temp storage, since Image won't preserve knowledge of these
334 ///                  bonusBytes once the allocation is done.
335 ///     clearWithZeroes Should fill the whole allocated area with zeroes (so that includes borders and gap bytes).
336 ///     dataPointer  The pointer to the first scanline.
337 ///     mallocArea   The pointer to the allocation beginning. Will be different from dataPointer and
338 ///                  must be kept somewhere.
339 ///     pitchBytes   Byte offset between two adjacent scanlines. Scanlines cannot ever overlap.
340 ///     layerOffset  Byte offset between two adjacent layers. Return 0 if only one layer.
341 ///     err          True if successful. Only err indicates success, not mallocArea.
342 ///
343 /// Note: even if you can request zero bytes, `realloc` can give you a non-null pointer, 
344 /// that you would have to keep. This is a success case given by `err` only.
345 void allocatePixelStorage(ubyte* existingData, 
346                           PixelType type, 
347                           int layers,
348                           int width, 
349                           int height, 
350                           LayoutConstraints constraints,
351                           int bonusBytes,
352                           bool clearWithZeroes,
353                           out ubyte* dataPointer, // first scanline
354                           out ubyte* mallocArea,  // the result of realloc-ed
355                           out int pitchBytes,
356                           out int layerOffset,
357                           out bool err) pure @trusted
358 {
359     assert(layers >= 0); // layers == 0 must be supported!
360     assert(width >= 0); // width == 0 and height == 0 must be supported!
361     assert(height >= 0);
362     assert(layoutConstraintsValid(constraints));
363 
364     // Width and height must be within limits.
365     if (!imageIsValidSize(layers, width, height))
366     {
367         err = true;
368         return;
369     }
370 
371     int border         = layoutBorderWidth(constraints);
372     int rowAlignment   = layoutScanlineAlignment(constraints);
373     int trailingPixels = layoutTrailingPixels(constraints);
374     int xMultiplicity  = layoutMultiplicity(constraints);
375     bool gapless       = layoutGapless(constraints);
376 
377     assert(border >= 0);
378     assert(rowAlignment >= 1);
379     assert(xMultiplicity >= 1);
380     assert(trailingPixels >= 0);
381 
382     static size_t nextMultipleOf(size_t base, size_t multiple) pure
383     {
384         assert(multiple > 0);
385         size_t n = (base + multiple - 1) / multiple;
386         return multiple * n;
387     }
388 
389     static int computeRightPadding(int width, int border, int xMultiplicity) pure
390     {
391         int nextMultiple = cast(int)(nextMultipleOf(width + border, xMultiplicity));
392         return nextMultiple - (width + border);
393     }    
394 
395     /// Returns: next pointer aligned with alignment bytes.
396     static ubyte* nextAlignedPointer(ubyte* start, size_t alignment) pure
397     {
398         return cast(ubyte*)nextMultipleOf(cast(size_t)(start), alignment);
399     }
400 
401     // Compute size of right border, in pixels.
402     // How many "padding pixels" do we need to extend the right border with to respect `xMultiplicity`?
403     int rightPadding = computeRightPadding(width, border, xMultiplicity);
404     int borderRight = border + rightPadding;
405     if (borderRight < trailingPixels)
406         borderRight = trailingPixels;
407 
408     int actualWidthInPixels  = border + width  + borderRight;
409 
410     // Support layers: border exists for each layer.
411     long actualHeightOfOneLayer = cast(long)(border + height + border);
412     long actualHeightInPixels = actualHeightOfOneLayer * layers;
413 
414     // Compute byte pitch and align it on `rowAlignment`
415     int pixelSize = pixelTypeSize(type);
416 
417     // No overflow, actualWidthInPixels is at a maximum (1<<24)+16
418     //                            and pixel size is at a maxium 16.
419     int bytePitch = pixelSize * actualWidthInPixels;
420     bytePitch = cast(int) nextMultipleOf(bytePitch, rowAlignment);
421 
422     assert(bytePitch >= 0);
423 
424     // Could overflow to 64-bit here.
425     long sizeNeededBytes = cast(long)bytePitch * cast(long)actualHeightInPixels;
426     sizeNeededBytes += (rowAlignment - 1) + bonusBytes;
427     
428     // A gamut image can't take more than `GAMUT_MAX_IMAGE_BYTES` bytes
429     // in its allocation, including bonus bytes and constraint bytes.
430     if (sizeNeededBytes > GAMUT_MAX_IMAGE_BYTES)
431     {
432         err = true;
433         return;
434     }
435 
436     // And the allocation length must fit in a size_t variable.
437     if (sizeNeededBytes >= size_t.max)
438     {
439         err = true; // too large image in 32-bit, typically
440         return;
441     }
442 
443     // How many bytes do we need for all samples? A bit more for aligning the first valid pixel.
444     size_t allocationSize = cast(size_t) sizeNeededBytes;
445 
446     // PERF: on Windows, reusing previous allocation is much faster for same alloc size
447     //       314x faster             vs free+malloc for same size
448     //       10x faster              vs free+malloc for decreasing size 1 by 1
449     //       424x slower (quadratic) vs free+malloc for increasing size 1 by 1
450     //       0.5x slower             vs free+malloc for random size
451     // If we store the allocation as slice, we could use the right way to realloc everytime.
452     enum ENABLE_REALLOC = true; // TODO
453 
454     ubyte* allocation;
455     static if (ENABLE_REALLOC)
456     {
457         // We don't need to preserve former data, nor to align the allocation.
458         // Note: allocationSize can legally be zero.
459 
460         // TODO: manages C23 allocationSize == 0 here
461         allocation = cast(ubyte*) fakePureRealloc(existingData, allocationSize);
462     }
463     else
464     {
465         free(existingData);
466         existingData = null;
467         allocation = cast(ubyte*) fakePureMalloc(allocationSize);
468     }
469 
470     // realloc is allowed to return null if zero bytes required.
471     if (allocationSize != 0 && allocation is null) 
472     {
473         err = true;
474         return;
475     }
476 
477     // Optional by zero clearing of allocation area.
478     // PERF: realloc made a useless copy in that case.
479     if (clearWithZeroes && (allocationSize > 0))
480     {
481         memset(allocation, 0, allocationSize);
482     }
483 
484     // Compute pointer to pixel data itself.
485     size_t offsetToFirstMeaningfulPixel = bonusBytes + bytePitch * border + pixelSize * border;       
486     ubyte* pixels = nextAlignedPointer(allocation + offsetToFirstMeaningfulPixel, rowAlignment);
487 
488     // Apply vertical constraints: will the image be stored upside-down?
489     ubyte* firstScanlinePtr = pixels;
490     int finalPitchInBytes = bytePitch;
491     applyVFlipConstraintsToScanlinePointers(width, height, firstScanlinePtr, finalPitchInBytes, constraints);
492 
493     dataPointer = firstScanlinePtr;
494     pitchBytes = finalPitchInBytes;
495     if (layers == 0 || layers == 1)
496     {
497         layerOffset = 0;
498     }
499     else
500     {
501         long offsetBetweenLayers = bytePitch * actualHeightOfOneLayer;
502         if (offsetBetweenLayers > layerOffset.max)
503         {
504             // TODO: what should be the maximum possible layerOffset? Should
505             // probably be size_t?
506             err = true;
507             return;
508         }
509         layerOffset = cast(int)offsetBetweenLayers;
510     }
511     mallocArea = allocation;
512     err = false;
513 
514     // Check validity of result
515     {
516         // check gapless
517         int scanWidth = pixelSize * width;
518         if (gapless)
519             assert(scanWidth == (bytePitch < 0 ? -bytePitch : bytePitch));
520         
521         // check row alignment
522         static bool isPointerAligned(void* p, size_t alignment) pure
523         {
524             assert(alignment != 0);
525             return ( cast(size_t)p & (alignment - 1) ) == 0;
526         }        
527         assert(isPointerAligned(dataPointer, rowAlignment));
528         assert(isPointerAligned(dataPointer + pitchBytes, rowAlignment));
529     }
530 }
531 
532 // Deallocate pixel data. Everything allocated with `allocatePixelStorage` eventually needs
533 // to be through that function.
534 void deallocatePixelStorage(void* mallocArea) pure @system
535 {
536     fakePureFree(mallocArea);
537 }
538 
539 // Deallocate encoded image.
540 void deallocateEncodedImage(ubyte[] encodedImage) pure @system
541 {
542     fakePureFree(encodedImage.ptr);
543 }
544 
545 // Pretend to be pure.
546 extern (C) private pure @system @nogc nothrow
547 {
548     pragma(mangle, "malloc")  void* fakePureMalloc(size_t);
549     pragma(mangle, "realloc") void* fakePureRealloc(void* ptr, size_t size);
550     pragma(mangle, "free")    void  fakePureFree(void* ptr);
551 }
552 
553 bool validLoadFlags(LoadFlags loadFlags) pure
554 {
555     if ((loadFlags & LOAD_GREYSCALE) && (loadFlags & LOAD_RGB)) return false;
556     if ((loadFlags & LOAD_ALPHA) && (loadFlags & LOAD_NO_ALPHA)) return false;
557 
558     int bitnessFlags = 0;
559     if (loadFlags & LOAD_8BIT) ++bitnessFlags;
560     if (loadFlags & LOAD_16BIT) ++bitnessFlags;
561     if (loadFlags & LOAD_FP32) ++bitnessFlags;
562     if (bitnessFlags > 1)
563         return false;
564 
565     return true;
566 }
567 
568 
569 // Load flags to components asked, intended for an image decoder. 
570 // This is for STB-style loading who can convert scanline as they are decoded.
571 //
572 // Return: 
573 //   -1 => keep input number of components
574 //    0 => error
575 //    1/2/3/4 => forced number of components.
576 int computeRequestedImageComponents(LoadFlags loadFlags) pure nothrow @nogc @safe
577 {
578     int requestedComp = -1; // keep original
579 
580     if (!validLoadFlags(loadFlags))
581         return 0;
582 
583     if (loadFlags & LOAD_GREYSCALE)
584     {
585         if (loadFlags & LOAD_ALPHA)
586             requestedComp = 2;
587         else if (loadFlags & LOAD_NO_ALPHA)
588             requestedComp = 1;
589     }
590     else if (loadFlags & LOAD_RGB)
591     {
592         if (loadFlags & LOAD_ALPHA)
593             requestedComp = 4;
594         else if (loadFlags & LOAD_NO_ALPHA)
595             requestedComp = 3;
596     }
597     return requestedComp;
598 }
599 unittest
600 {
601     assert(computeRequestedImageComponents(LOAD_GREYSCALE) == -1); // keep same, because it is alpha-preserving.
602     assert(computeRequestedImageComponents(LOAD_GREYSCALE | LOAD_NO_ALPHA) == 1);
603     assert(computeRequestedImageComponents(LOAD_GREYSCALE | LOAD_ALPHA) == 2);
604     assert(computeRequestedImageComponents(LOAD_GREYSCALE | LOAD_ALPHA | LOAD_NO_ALPHA) == 0); // invalid
605     assert(computeRequestedImageComponents(LOAD_RGB) == -1);
606     assert(computeRequestedImageComponents(LOAD_RGB | LOAD_NO_ALPHA) == 3);
607     assert(computeRequestedImageComponents(LOAD_RGB | LOAD_GREYSCALE) == 0); // invalid
608     assert(computeRequestedImageComponents(LOAD_RGB | LOAD_ALPHA) == 4);
609 }
610 
611 
612 // Type conversion.
613 
614 /// From a type and LoadFlags, get the target type using the loading flags.
615 /// This is when the decoder doesn't support inside conversion and we need to use convertTo.
616 PixelType applyLoadFlags(PixelType type, LoadFlags flags)
617 {
618     // Check incompatible load flags.
619     if (!validLoadFlags(flags))
620         return PixelType.unknown;
621 
622     if (flags & LOAD_GREYSCALE)
623         type = convertPixelTypeToGreyscale(type);
624 
625     if (flags & LOAD_RGB)
626         type = convertPixelTypeToRGB(type);
627 
628     if (flags & LOAD_ALPHA)
629         type = convertPixelTypeToAddAlphaChannel(type);
630 
631     if (flags & LOAD_NO_ALPHA)
632         type = convertPixelTypeToDropAlphaChannel(type);
633 
634     if (flags & LOAD_8BIT)
635         type = convertPixelTypeTo8Bit(type);
636 
637     if (flags & LOAD_16BIT)
638         type = convertPixelTypeTo16Bit(type);
639 
640     if (flags & LOAD_FP32)
641         type = convertPixelTypeToFP32(type);
642 
643     return type;
644 }
645