1 : /******************************************************************************
2 : * $Id: vrtfilters.cpp 17636 2009-09-12 23:19:18Z warmerdam $
3 : *
4 : * Project: Virtual GDAL Datasets
5 : * Purpose: Implementation of some filter types.
6 : * Author: Frank Warmerdam <warmerdam@pobox.com>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2003, Frank Warmerdam <warmerdam@pobox.com>
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "vrtdataset.h"
31 : #include "cpl_minixml.h"
32 : #include "cpl_string.h"
33 :
34 : CPL_CVSID("$Id: vrtfilters.cpp 17636 2009-09-12 23:19:18Z warmerdam $");
35 :
36 : /************************************************************************/
37 : /* ==================================================================== */
38 : /* VRTFilteredSource */
39 : /* ==================================================================== */
40 : /************************************************************************/
41 :
42 : /************************************************************************/
43 : /* VRTFilteredSource() */
44 : /************************************************************************/
45 :
46 5 : VRTFilteredSource::VRTFilteredSource()
47 :
48 : {
49 5 : nExtraEdgePixels = 0;
50 5 : nSupportedTypesCount = 1;
51 5 : aeSupportedTypes[0] = GDT_Float32;
52 5 : }
53 :
54 : /************************************************************************/
55 : /* ~VRTFilteredSource() */
56 : /************************************************************************/
57 :
58 5 : VRTFilteredSource::~VRTFilteredSource()
59 :
60 : {
61 5 : }
62 :
63 : /************************************************************************/
64 : /* SetExtraEdgePixels() */
65 : /************************************************************************/
66 :
67 5 : void VRTFilteredSource::SetExtraEdgePixels( int nEdgePixels )
68 :
69 : {
70 5 : nExtraEdgePixels = nEdgePixels;
71 5 : }
72 :
73 : /************************************************************************/
74 : /* SetFilteringDataTypesSupported() */
75 : /************************************************************************/
76 :
77 5 : void VRTFilteredSource::SetFilteringDataTypesSupported( int nTypeCount,
78 : GDALDataType *paeTypes)
79 :
80 : {
81 5 : if( nTypeCount >
82 : (int) sizeof(sizeof(aeSupportedTypes)/sizeof(GDALDataType)) )
83 : {
84 : CPLAssert( FALSE );
85 : nTypeCount = (int)
86 0 : sizeof(sizeof(aeSupportedTypes)/sizeof(GDALDataType));
87 : }
88 :
89 5 : nSupportedTypesCount = nTypeCount;
90 5 : memcpy( aeSupportedTypes, paeTypes, sizeof(GDALDataType) * nTypeCount );
91 5 : }
92 :
93 : /************************************************************************/
94 : /* IsTypeSupported() */
95 : /************************************************************************/
96 :
97 440 : int VRTFilteredSource::IsTypeSupported( GDALDataType eTestType )
98 :
99 : {
100 : int i;
101 :
102 880 : for( i = 0; i < nSupportedTypesCount; i++ )
103 : {
104 440 : if( eTestType == aeSupportedTypes[i] )
105 0 : return TRUE;
106 : }
107 :
108 440 : return FALSE;
109 : }
110 :
111 : /************************************************************************/
112 : /* RasterIO() */
113 : /************************************************************************/
114 :
115 : CPLErr
116 220 : VRTFilteredSource::RasterIO( int nXOff, int nYOff, int nXSize, int nYSize,
117 : void *pData, int nBufXSize, int nBufYSize,
118 : GDALDataType eBufType,
119 : int nPixelSpace, int nLineSpace )
120 :
121 : {
122 : /* -------------------------------------------------------------------- */
123 : /* For now we don't support filtered access to non-full */
124 : /* resolution requests. Just collect the data directly without */
125 : /* any operator. */
126 : /* -------------------------------------------------------------------- */
127 220 : if( nBufXSize != nXSize || nBufYSize != nYSize )
128 : {
129 : return VRTComplexSource::RasterIO( nXOff, nYOff, nXSize, nYSize,
130 : pData, nBufXSize, nBufYSize,
131 0 : eBufType, nPixelSpace, nLineSpace );
132 : }
133 :
134 : /* -------------------------------------------------------------------- */
135 : /* Determine the data type we want to request. We try to match */
136 : /* the source or destination request, and if both those fail we */
137 : /* fallback to the first supported type at least as expressive */
138 : /* as the request. */
139 : /* -------------------------------------------------------------------- */
140 220 : GDALDataType eOperDataType = GDT_Unknown;
141 : int i;
142 :
143 220 : if( IsTypeSupported( eBufType ) )
144 0 : eOperDataType = eBufType;
145 :
146 220 : if( eOperDataType == GDT_Unknown
147 : && IsTypeSupported( poRasterBand->GetRasterDataType() ) )
148 0 : eOperDataType = poRasterBand->GetRasterDataType();
149 :
150 220 : if( eOperDataType == GDT_Unknown )
151 : {
152 440 : for( i = 0; i < nSupportedTypesCount; i++ )
153 : {
154 440 : if( GDALDataTypeUnion( aeSupportedTypes[i], eBufType )
155 220 : == aeSupportedTypes[i] )
156 : {
157 220 : eOperDataType = aeSupportedTypes[i];
158 : }
159 : }
160 : }
161 :
162 220 : if( eOperDataType == GDT_Unknown )
163 : {
164 0 : eOperDataType = aeSupportedTypes[0];
165 :
166 0 : for( i = 1; i < nSupportedTypesCount; i++ )
167 : {
168 0 : if( GDALGetDataTypeSize( aeSupportedTypes[i] )
169 : > GDALGetDataTypeSize( eOperDataType ) )
170 : {
171 0 : eOperDataType = aeSupportedTypes[i];
172 : }
173 : }
174 : }
175 :
176 : /* -------------------------------------------------------------------- */
177 : /* Allocate the buffer of data into which our imagery will be */
178 : /* read, with the extra edge pixels as well. This will be the */
179 : /* source data fed into the filter. */
180 : /* -------------------------------------------------------------------- */
181 : int nPixelOffset, nLineOffset;
182 220 : int nExtraXSize = nBufXSize + 2 * nExtraEdgePixels;
183 220 : int nExtraYSize = nBufYSize + 2 * nExtraEdgePixels;
184 : GByte *pabyWorkData;
185 :
186 : // FIXME? : risk of multiplication overflow
187 : pabyWorkData = (GByte *)
188 : VSICalloc( nExtraXSize * nExtraYSize,
189 220 : (GDALGetDataTypeSize(eOperDataType) / 8) );
190 :
191 220 : if( pabyWorkData == NULL )
192 : {
193 : CPLError( CE_Failure, CPLE_OutOfMemory,
194 0 : "Work buffer allocation failed." );
195 0 : return CE_Failure;
196 : }
197 :
198 220 : nPixelOffset = GDALGetDataTypeSize( eOperDataType ) / 8;
199 220 : nLineOffset = nPixelOffset * nExtraXSize;
200 :
201 : /* -------------------------------------------------------------------- */
202 : /* Allocate the output buffer if the passed in output buffer is */
203 : /* not of the same type as our working format, or if the passed */
204 : /* in buffer has an unusual organization. */
205 : /* -------------------------------------------------------------------- */
206 : GByte *pabyOutData;
207 :
208 440 : if( nPixelSpace != nPixelOffset || nLineSpace != nLineOffset
209 : || eOperDataType != eBufType )
210 : {
211 : pabyOutData = (GByte *)
212 220 : VSIMalloc3(nBufXSize, nBufYSize, nPixelOffset );
213 :
214 220 : if( pabyOutData == NULL )
215 : {
216 : CPLError( CE_Failure, CPLE_OutOfMemory,
217 0 : "Work buffer allocation failed." );
218 0 : return CE_Failure;
219 : }
220 : }
221 : else
222 0 : pabyOutData = (GByte *) pData;
223 :
224 : /* -------------------------------------------------------------------- */
225 : /* Figure out the extended window that we want to load. Note */
226 : /* that we keep track of the file window as well as the amount */
227 : /* we will need to edge fill past the edge of the source dataset. */
228 : /* -------------------------------------------------------------------- */
229 220 : int nTopFill=0, nLeftFill=0, nRightFill=0, nBottomFill=0;
230 : int nFileXOff, nFileYOff, nFileXSize, nFileYSize;
231 :
232 220 : nFileXOff = nXOff - nExtraEdgePixels;
233 220 : nFileYOff = nYOff - nExtraEdgePixels;
234 220 : nFileXSize = nExtraXSize;
235 220 : nFileYSize = nExtraYSize;
236 :
237 220 : if( nFileXOff < 0 )
238 : {
239 220 : nLeftFill = -nFileXOff;
240 220 : nFileXOff = 0;
241 220 : nFileXSize -= nLeftFill;
242 : }
243 :
244 220 : if( nFileYOff < 0 )
245 : {
246 5 : nTopFill = -nFileYOff;
247 5 : nFileYOff = 0;
248 5 : nFileYSize -= nTopFill;
249 : }
250 :
251 220 : if( nFileXOff + nFileXSize > poRasterBand->GetXSize() )
252 : {
253 220 : nRightFill = nFileXOff + nFileXSize - poRasterBand->GetXSize();
254 220 : nFileXSize -= nRightFill;
255 : }
256 :
257 220 : if( nFileYOff + nFileYSize > poRasterBand->GetYSize() )
258 : {
259 5 : nBottomFill = nFileYOff + nFileYSize - poRasterBand->GetYSize();
260 5 : nFileYSize -= nBottomFill;
261 : }
262 :
263 : /* -------------------------------------------------------------------- */
264 : /* Load the data. */
265 : /* -------------------------------------------------------------------- */
266 : CPLErr eErr;
267 :
268 : eErr =
269 : VRTComplexSource::RasterIO( nFileXOff, nFileYOff, nFileXSize, nFileYSize,
270 : pabyWorkData
271 : + nLineOffset * nTopFill
272 : + nPixelOffset * nLeftFill,
273 : nFileXSize, nFileYSize, eOperDataType,
274 220 : nPixelOffset, nLineOffset );
275 :
276 220 : if( eErr != CE_None )
277 : {
278 0 : if( pabyWorkData != pData )
279 0 : VSIFree( pabyWorkData );
280 :
281 0 : return eErr;
282 : }
283 :
284 : /* -------------------------------------------------------------------- */
285 : /* Fill in missing areas. Note that we replicate the edge */
286 : /* valid values out. We don't using "mirroring" which might be */
287 : /* more suitable for some times of filters. We also don't mark */
288 : /* these pixels as "nodata" though perhaps we should. */
289 : /* -------------------------------------------------------------------- */
290 220 : if( nLeftFill != 0 || nRightFill != 0 )
291 : {
292 870 : for( i = nTopFill; i < nExtraYSize - nBottomFill; i++ )
293 : {
294 650 : if( nLeftFill != 0 )
295 : GDALCopyWords( pabyWorkData + nPixelOffset * nLeftFill
296 : + i * nLineOffset, eOperDataType, 0,
297 : pabyWorkData + i * nLineOffset, eOperDataType,
298 650 : nPixelOffset, nLeftFill );
299 :
300 650 : if( nRightFill != 0 )
301 : GDALCopyWords( pabyWorkData + i * nLineOffset
302 : + nPixelOffset * (nExtraXSize - nRightFill - 1),
303 : eOperDataType, 0,
304 : pabyWorkData + i * nLineOffset
305 : + nPixelOffset * (nExtraXSize - nRightFill),
306 650 : eOperDataType, nPixelOffset, nRightFill );
307 : }
308 : }
309 :
310 225 : for( i = 0; i < nTopFill; i++ )
311 : {
312 : memcpy( pabyWorkData + i * nLineOffset,
313 : pabyWorkData + nTopFill * nLineOffset,
314 5 : nLineOffset );
315 : }
316 :
317 225 : for( i = nExtraYSize - nBottomFill; i < nExtraYSize; i++ )
318 : {
319 : memcpy( pabyWorkData + i * nLineOffset,
320 : pabyWorkData + (nExtraYSize - nBottomFill - 1) * nLineOffset,
321 5 : nLineOffset );
322 : }
323 :
324 : /* -------------------------------------------------------------------- */
325 : /* Filter the data. */
326 : /* -------------------------------------------------------------------- */
327 : eErr = FilterData( nBufXSize, nBufYSize, eOperDataType,
328 220 : pabyWorkData, pabyOutData );
329 :
330 220 : VSIFree( pabyWorkData );
331 220 : if( eErr != CE_None )
332 : {
333 0 : if( pabyOutData != pData )
334 0 : VSIFree( pabyOutData );
335 :
336 0 : return eErr;
337 : }
338 :
339 : /* -------------------------------------------------------------------- */
340 : /* Copy from work buffer to target buffer. */
341 : /* -------------------------------------------------------------------- */
342 220 : if( pabyOutData != pData )
343 : {
344 440 : for( i = 0; i < nBufYSize; i++ )
345 : {
346 : GDALCopyWords( pabyOutData + i * (nPixelOffset * nBufXSize),
347 : eOperDataType, nPixelOffset,
348 : ((GByte *) pData) + i * nLineSpace,
349 220 : eBufType, nPixelSpace, nBufXSize );
350 : }
351 :
352 220 : VSIFree( pabyOutData );
353 : }
354 :
355 220 : return CE_None;
356 : }
357 :
358 : /************************************************************************/
359 : /* ==================================================================== */
360 : /* VRTKernelFilteredSource */
361 : /* ==================================================================== */
362 : /************************************************************************/
363 :
364 : /************************************************************************/
365 : /* VRTKernelFilteredSource() */
366 : /************************************************************************/
367 :
368 5 : VRTKernelFilteredSource::VRTKernelFilteredSource()
369 :
370 : {
371 5 : GDALDataType aeSupTypes[] = { GDT_Float32 };
372 5 : padfKernelCoefs = NULL;
373 5 : nKernelSize = 0;
374 5 : bNormalized = FALSE;
375 :
376 5 : SetFilteringDataTypesSupported( 1, aeSupTypes );
377 5 : }
378 :
379 : /************************************************************************/
380 : /* ~VRTKernelFilteredSource() */
381 : /************************************************************************/
382 :
383 10 : VRTKernelFilteredSource::~VRTKernelFilteredSource()
384 :
385 : {
386 5 : CPLFree( padfKernelCoefs );
387 10 : }
388 :
389 : /************************************************************************/
390 : /* SetNormalized() */
391 : /************************************************************************/
392 :
393 5 : void VRTKernelFilteredSource::SetNormalized( int bNormalizedIn )
394 :
395 : {
396 5 : bNormalized = bNormalizedIn;
397 5 : }
398 :
399 : /************************************************************************/
400 : /* SetKernel() */
401 : /************************************************************************/
402 :
403 5 : CPLErr VRTKernelFilteredSource::SetKernel( int nNewKernelSize,
404 : double *padfNewCoefs )
405 :
406 : {
407 5 : if( nNewKernelSize < 1 || (nNewKernelSize % 2) != 1 )
408 : {
409 : CPLError( CE_Failure, CPLE_AppDefined,
410 : "Illegal filtering kernel size %d, must be odd positive number.",
411 0 : nNewKernelSize );
412 0 : return CE_Failure;
413 : }
414 :
415 5 : CPLFree( padfKernelCoefs );
416 5 : nKernelSize = nNewKernelSize;
417 :
418 : padfKernelCoefs = (double *)
419 5 : CPLMalloc(sizeof(double) * nKernelSize * nKernelSize );
420 : memcpy( padfKernelCoefs, padfNewCoefs,
421 5 : sizeof(double) * nKernelSize * nKernelSize );
422 :
423 5 : SetExtraEdgePixels( (nNewKernelSize - 1) / 2 );
424 :
425 5 : return CE_None;
426 : }
427 :
428 : /************************************************************************/
429 : /* FilterData() */
430 : /************************************************************************/
431 :
432 220 : CPLErr VRTKernelFilteredSource::
433 : FilterData( int nXSize, int nYSize, GDALDataType eType,
434 : GByte *pabySrcData, GByte *pabyDstData )
435 :
436 : {
437 : /* -------------------------------------------------------------------- */
438 : /* Validate data type. */
439 : /* -------------------------------------------------------------------- */
440 220 : if( eType != GDT_Float32 )
441 : {
442 : CPLError( CE_Failure, CPLE_AppDefined,
443 : "Unsupported data type (%s) in VRTKernelFilteredSource::FilterData()",
444 0 : GDALGetDataTypeName( eType ) );
445 0 : return CE_Failure;
446 : }
447 :
448 : CPLAssert( nExtraEdgePixels*2 + 1 == nKernelSize );
449 :
450 : /* -------------------------------------------------------------------- */
451 : /* Float32 case. */
452 : /* -------------------------------------------------------------------- */
453 220 : if( eType == GDT_Float32 )
454 : {
455 : int iX, iY;
456 :
457 : int bHasNoData;
458 220 : float fNoData = (float) poRasterBand->GetNoDataValue(&bHasNoData);
459 :
460 440 : for( iY = 0; iY < nYSize; iY++ )
461 : {
462 10620 : for( iX = 0; iX < nXSize; iX++ )
463 : {
464 10400 : int iYY, iKern = 0;
465 10400 : double dfSum = 0.0, dfKernSum = 0.0;
466 : float fResult;
467 10400 : int iIndex = (iY+nKernelSize/2 ) * (nXSize+2*nExtraEdgePixels) + iX + nKernelSize/2;
468 10400 : float fCenter = ((float *)pabySrcData)[iIndex];
469 :
470 : // Check if center srcpixel is NoData
471 20600 : if(!bHasNoData || fCenter != fNoData)
472 : {
473 40800 : for( iYY = 0; iYY < nKernelSize; iYY++ )
474 : {
475 : int i;
476 : float *pafData = ((float *)pabySrcData)
477 30600 : + (iY+iYY) * (nXSize+2*nExtraEdgePixels) + iX;
478 :
479 122400 : for( i = 0; i < nKernelSize; i++, pafData++, iKern++ )
480 : {
481 91800 : if(!bHasNoData || *pafData != fNoData)
482 : {
483 90964 : dfSum += *pafData * padfKernelCoefs[iKern];
484 90964 : dfKernSum += padfKernelCoefs[iKern];
485 : }
486 : }
487 : }
488 10200 : if( bNormalized )
489 : {
490 200 : if( dfKernSum != 0.0 )
491 200 : fResult = (float) (dfSum / dfKernSum);
492 : else
493 0 : fResult = 0.0;
494 : }
495 : else
496 10000 : fResult = (float) dfSum;
497 :
498 10200 : ((float *) pabyDstData)[iX + iY * nXSize] = fResult;
499 : }
500 : else
501 200 : ((float *) pabyDstData)[iX + iY * nXSize] = fNoData;
502 : }
503 : }
504 : }
505 :
506 220 : return CE_None;
507 : }
508 :
509 : /************************************************************************/
510 : /* XMLInit() */
511 : /************************************************************************/
512 :
513 5 : CPLErr VRTKernelFilteredSource::XMLInit( CPLXMLNode *psTree,
514 : const char *pszVRTPath )
515 :
516 : {
517 5 : CPLErr eErr = VRTFilteredSource::XMLInit( psTree, pszVRTPath );
518 : int nNewKernelSize, i, nCoefs;
519 : double *padfNewCoefs;
520 :
521 5 : if( eErr != CE_None )
522 0 : return eErr;
523 :
524 5 : nNewKernelSize = atoi(CPLGetXMLValue(psTree,"Kernel.Size","0"));
525 :
526 5 : if( nNewKernelSize == 0 )
527 0 : return CE_None;
528 :
529 : char **papszCoefItems =
530 5 : CSLTokenizeString( CPLGetXMLValue(psTree,"Kernel.Coefs","") );
531 :
532 5 : nCoefs = CSLCount(papszCoefItems);
533 :
534 5 : if( nCoefs != nNewKernelSize * nNewKernelSize )
535 : {
536 0 : CSLDestroy( papszCoefItems );
537 : CPLError( CE_Failure, CPLE_AppDefined,
538 : "Got wrong number of filter kernel coefficients (%s).\n"
539 : "Expected %d, got %d.",
540 : CPLGetXMLValue(psTree,"Kernel.Coefs",""),
541 0 : nNewKernelSize * nNewKernelSize, nCoefs );
542 0 : return CE_Failure;
543 : }
544 :
545 5 : padfNewCoefs = (double *) CPLMalloc(sizeof(double) * nCoefs);
546 :
547 50 : for( i = 0; i < nCoefs; i++ )
548 45 : padfNewCoefs[i] = atof(papszCoefItems[i]);
549 :
550 5 : eErr = SetKernel( nNewKernelSize, padfNewCoefs );
551 :
552 5 : CPLFree( padfNewCoefs );
553 5 : CSLDestroy( papszCoefItems );
554 :
555 5 : SetNormalized( atoi(CPLGetXMLValue(psTree,"Kernel.normalized","0")) );
556 :
557 5 : return eErr;
558 : }
559 :
560 : /************************************************************************/
561 : /* SerializeToXML() */
562 : /************************************************************************/
563 :
564 0 : CPLXMLNode *VRTKernelFilteredSource::SerializeToXML( const char *pszVRTPath )
565 :
566 : {
567 0 : CPLXMLNode *psSrc = VRTFilteredSource::SerializeToXML( pszVRTPath );
568 : CPLXMLNode *psKernel;
569 : char *pszKernelCoefs;
570 0 : int iCoef, nCoefCount = nKernelSize * nKernelSize;
571 :
572 0 : if( psSrc == NULL )
573 0 : return NULL;
574 :
575 0 : CPLFree( psSrc->pszValue );
576 0 : psSrc->pszValue = CPLStrdup("KernelFilteredSource" );
577 :
578 0 : psKernel = CPLCreateXMLNode( psSrc, CXT_Element, "Kernel" );
579 :
580 0 : if( bNormalized )
581 : CPLCreateXMLNode(
582 : CPLCreateXMLNode( psKernel, CXT_Attribute, "normalized" ),
583 0 : CXT_Text, "1" );
584 : else
585 : CPLCreateXMLNode(
586 : CPLCreateXMLNode( psKernel, CXT_Attribute, "normalized" ),
587 0 : CXT_Text, "0" );
588 :
589 0 : pszKernelCoefs = (char *) CPLMalloc(nCoefCount * 32);
590 :
591 0 : strcpy( pszKernelCoefs, "" );
592 0 : for( iCoef = 0; iCoef < nCoefCount; iCoef++ )
593 : sprintf( pszKernelCoefs + strlen(pszKernelCoefs),
594 0 : "%.8g ", padfKernelCoefs[iCoef] );
595 :
596 0 : CPLSetXMLValue( psKernel, "Size", CPLSPrintf( "%d", nKernelSize ) );
597 0 : CPLSetXMLValue( psKernel, "Coefs", pszKernelCoefs );
598 :
599 0 : CPLFree( pszKernelCoefs );
600 :
601 0 : return psSrc;
602 : }
603 :
604 : /************************************************************************/
605 : /* VRTParseFilterSources() */
606 : /************************************************************************/
607 :
608 5 : VRTSource *VRTParseFilterSources( CPLXMLNode *psChild, const char *pszVRTPath )
609 :
610 : {
611 : VRTSource *poSrc;
612 :
613 5 : if( EQUAL(psChild->pszValue,"KernelFilteredSource") )
614 : {
615 5 : poSrc = new VRTKernelFilteredSource();
616 5 : if( poSrc->XMLInit( psChild, pszVRTPath ) == CE_None )
617 5 : return poSrc;
618 : else
619 0 : delete poSrc;
620 : }
621 :
622 0 : return NULL;
623 : }
624 :
|