diff --git a/README.md b/README.md new file mode 100644 index 0000000..19fa232 --- /dev/null +++ b/README.md @@ -0,0 +1,125 @@ +This code is the author's implementation of the algorithm presented in: + +Carl Doersch, Abhinav Gupta, and Alexei A. Efros, "Mid Level Visual Element +Discovery as Discriminative Mode Seeking" in NIPS 2013. + +The majority of the code was written by Carl Doersch (cdoersch at cs dot cmu dot edu), +although there are major contributions from Saurabh Singh and some from others who +are noted in the code. + +This is officially unsupported research code, but our goal is that this will be +as useful as possible. You are encouraged to ask questions via e-mail, +and strongly encouraged to give feedback if you find the code +counter-intuitive. I plan to update this code as issues are discovered. + +General setup: + +0) This code is only designed to run on linux. It might run on a mac or other + unix system since it only uses unix commands, but it has not been tested. + +1) Install libsvm and edit the file 'myaddpath.m' so that it adds libsvm to the path. + +2) Check that you have the statistics toolbox. If you plan to use the inter-element + communication and are not running 64-bit linux, you need to copy the file + [MATLAB ROOT]/toolbox/stats/stats/private/linkagemex.(your extension) + onto the matlab path (e.g. in the directory containing this file). This is to + work around a bug I've experienced in Matlab's linkage implementation; if + you don't want to use the workaround, you can switch the call in findOverlapping3.m + from linkage2 to linkage. + +3) Edit the indoor67_main file with the path of your download of the indoor67 dataset + (there's directions in the file). If you want to use your own data, setdataset.m gives + instructions on how to do this. + +4) If you aren't running on 64-bit linux, go to the hog/ directory + and run 'mex features.cc'. + +5) [optional] If you want to measure coverage pixel-wise (the code doesn't by default), compile + the code in the clipper directory--i.e. cd into the directory and run + "mex greedySelectDetrsCoveragemex.cpp clipper.hpp clipper.cpp" + +6) Make the .matlab folder in your home directory read-only--i.e. 'chmod -R 555 ~/.matlab'. + The code may work without this, but by default matlab writes a toolbox cache file + to the home directory when it starts up. If many matlabs start at the same time, + they can corrupt this file and cause some matlab processes to error out. The dswork + code restarts the workers occasionally, which means the execution will get stuck if + matlab can no longer start properly. + +7) Run the clustering code in Matlab. The main script for the indoor67 experiment + is indoor67_main.m. + +Running the code on indoor67 should require about 8GB of RAM per machine. indoor67_main +will attempt to estimate the number of jobs to run on each machine based on the RAM +each machine has. The program also needs about 30GB of free disk space and about 300GB +in the temporary local directories (that's total; it can be distributed across different +machines). If you're not using local directories, then that 300GB will need to be on +the shared filesystem. + + +Parallelizing the code with dswork: + +This codebase uses heavily the dswork framework. The README in the dswork +directory gives full documentation, but here's a tl;dr summary. + +dswork has two main features. (1) it establishes a mapping between +some directory on the filesystem and the variable 'ds' in your +workspace. Hence, you can call + +dssetout('/tmp'); +ds.mydirectory.myvariable=rand(100); +dssave; + +This causes the variable ds.myvariable to be saved to '/tmp/ds/mydirectory/myvariable.mat'. +dswork supports filesystem command analogous to unix, including dsmv, dsdelete, +dssymlink (though this implementation is incomplete), and dscd. To make the syntax +as concise as possible, the format that things are saved in depends on the variable +suffix--thus far, the suffixes img and html and txt have special meanings. + +On top of this, dswork supports some basic distributed processing features, including +multiple MATLAB's on one machine, and multiple matlabs on different machines. For these +to work, the directory where dswork saves its files needs to be shared among all machines +you are using. + +At a high level, dsmapredopen() sets up a pool of workers that are essentially stateless. +Using dsrundistributed() or dsmapreduce() will assign work to each worker, allows the +workers to load data from the shared storage, and tracks the variables that +get written. Additionally, dsmapreduce() supports mapreduce-like communication between +the workers that does not go through the shared storage. This instead uses ssh to +share data. This is important because, if you share all your data using an nfs-share +of a disk on a single machine (as I did), I/O will likely become a bottleneck for the +main element mining loop. Sharing data directly between machines alleviates this +issue. To use this, the only setup required should be to give dswork a local directory +where it can save temporary files on each machine, via the dssetlocaldir() funciton. + + +Running multiple matlab's locally should not require any additional setup, but running +distributed will require a machine that supports qsub. All of the experiments +for this project were performed using Starcluster on EC2, which sets up an +OGS cluster with data shared over nfs. See dsmapredopen for instructions on +starting the distributed session. + + +So you want to understand the code... + +My coding style is developed around rapid prototyping, and is probably different from +what you're used to. Here's a few patterns that I tend to use. + +1) I generally use parallel arrays where other programmers would use arrays of structs or + arrays of objects. This is the case because I often need quick access to all values of a single + field. Matlab's struct arrays support this, but it is extremely inefficient. + The distributeby/invertdistributeby have become a sort of swiss army knife for handling + parallel arrays in my code. You should memorize what distributeby does. + +2) To ease dealing with parallel arrays, the effstr... commands are designed to deal + with a struct holding multiple parallel arrays (effstr means 'efficient replacement + for matlab struct arrays'). The motivation is that I can add temporary data to an + object and keep track of it alongside those objects, all with minimal modification + of the code. + +3) If I have a collection of n bounding boxes, they will be stored in an n-by-8 array with + the following column order: [x1 y1 x2 y2 detection_score detector_id image_id flip]. + x- and y- coordinates are in terms of pixels *in the space returned by getimg*. flipped + detections have flip=1, but are still in terms of the coordinates of the un-flipped image. + These are used so frequently in the code that this format is used without comment; you + should memorize the order. + diff --git a/RGB2Lab.m b/RGB2Lab.m new file mode 100755 index 0000000..25692fe --- /dev/null +++ b/RGB2Lab.m @@ -0,0 +1,69 @@ +function [L,a,b] = RGB2Lab(color_image) +% function [L, a, b] = RGB2Lab(R, G, B) +% RGB2Lab takes matrices corresponding to Red, Green, and Blue, and +% transforms them into CIELab. This transform is based on ITU-R +% Recommendation BT.709 using the D65 white point reference. +% The error in transforming RGB -> Lab -> RGB is approximately +% 10^-5. RGB values can be either between 0 and 1 or between 0 and 255. +% By Mark Ruzon from C code by Yossi Rubner, 23 September 1997. +% Updated for MATLAB 5 28 January 1998. + +% commented out grayscale case +% if (nargin == 1) +% B = double(R(:,:,3)); +% G = double(R(:,:,2)); +% R = double(R(:,:,1)); +% end + +% commented out error checking, jhhays +% if ((max(max(R)) > 1.0) | (max(max(G)) > 1.0) | (max(max(B)) > 1.0)) +% R = R/255; +% G = G/255; +% B = B/255; +% end + +M = size(color_image,1); +N = size(color_image,2); +s = M*N; + +% Set a threshold +T = 0.008856; + +RGB = [reshape(color_image(:,:,1),1,s); ... + reshape(color_image(:,:,2),1,s); ... + reshape(color_image(:,:,3),1,s)]; + +% RGB to XYZ +MAT = [0.412453 0.357580 0.180423; + 0.212671 0.715160 0.072169; + 0.019334 0.119193 0.950227]; +XYZ = MAT * RGB; + +X = XYZ(1,:) / 0.950456; +Y = XYZ(2,:); +Z = XYZ(3,:) / 1.088754; + +XT = X > T; +YT = Y > T; +ZT = Z > T; + +fX = XT .* X.^(1/3) + (~XT) .* (7.787 .* X + 16/116); + +% Compute L +Y3 = Y.^(1/3); +fY = YT .* Y3 + (~YT) .* (7.787 .* Y + 16/116); +L = YT .* (116 * Y3 - 16.0) + (~YT) .* (903.3 * Y); + +fZ = ZT .* Z.^(1/3) + (~ZT) .* (7.787 .* Z + 16/116); + +% Compute a and b +a = 500 * (fX - fY); +b = 200 * (fY - fZ); + +L = reshape(L, M, N); +a = reshape(a, M, N); +b = reshape(b, M, N); + +if ((nargout == 1) | (nargout == 0)) + L = cat(3,L,a,b); +end diff --git a/TestImages.txt b/TestImages.txt new file mode 100755 index 0000000..77c7d4f --- /dev/null +++ b/TestImages.txt @@ -0,0 +1,1340 @@ +kitchen/int474.jpg +operating_room/operating_room_31_03_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0075.jpg +videostore/videoclub_05_14_flickr.jpg +poolinside/piscine_interieureee.jpg +videostore/blockbuster_08_10_flickr.jpg +poolinside/piscina_cubierta_07_19_altavista.jpg +mall/mall26.jpg +kindergarden/toddler.jpg +buffet/Buffet_1.jpg +hospitalroom/IMG_1482.jpg +library/scotland_library.jpg +inside_bus/inside_bus_093.jpg +bar/bar_0382.jpg +kindergarden/McMil_PicImg00011.jpg +dentaloffice/dentista_oficina_01_02_flickr.jpg +office/img_0013.jpg +videostore/3_1interior1.jpg +buffet/Buffet_Set_Up_gif.jpg +computerroom/SALLE_ORDINATEUR_NDC.jpg +grocerystore/800px_Rewe_supermarkt.jpg +cloister/cloister2_wc_gfdl.jpg +concert_hall/e8708arial.jpg +operating_room/operating_room_24_02_altavista.jpg +jewelleryshop/pandora_jewelry_store3_25_.jpg +laundromat/interior010.jpg +warehouse/warehouse_0398.jpg +gym/gimnasio_47_20_altavista.jpg +lobby/RenaissanceHotelAmsterdam_lobby.jpg +meeting_room/conferencerm2_gif.jpg +garage/IMG_0130.jpg +inside_bus/inside_bus_079.jpg +inside_subway/metropolitana_46_08_flickr.jpg +mall/room204.jpg +buffet/Holiday_Buffet_3.jpg +library/JPB_Library.jpg +restaurant/restaurante_24_20_altavista.jpg +inside_subway/inside_subway_0006.jpg +videostore/videoclub_01_24_flickr.jpg +inside_bus/inside_bus_065.jpg +children_room/OR_03_06_1002_56_l.jpg +grocerystore/supermarkt055.jpg +jewelleryshop/pamodificada_c_42_.jpg +mall/ins46.jpg +corridor/londres_023.jpg +hairsalon/1cb88b407105e7f93511d544d9b7_1_3.jpg +bookstore/Libreria_25_04_altavista.jpg +dentaloffice/302406458_d635d6851e.jpg +office/homeoff008.jpg +concert_hall/33_e_498.jpg +movietheater/816105903_94823d06a7_1__13.jpg +hairsalon/coiffeur0636yp5.jpg +elevator/elevator_google_0057.jpg +restaurant/room106.jpg +stairscase/AT_04_04_2000_26_l.jpg +artstudio/taller_de_arte_03_03_altavista.jpg +elevator/elevator_google_0099.jpg +hairsalon/HS_partners.jpg +office/homeoff007.jpg +children_room/kids2_46_.jpg +grocerystore/2fgr_supermarkt.jpg +bathroom/room311.jpg +gameroom/salle_de_jeux_07_19_altavista.jpg +locker_room/locker_room_google_0125.jpg +lobby/Cascades_lobby_sized.jpg +nursery/modern_fish.jpg +waitingroom/Waiting_Room093.jpg +hairsalon/salon2678.jpg +winecellar/cave_vin_21_13_altavista.jpg +locker_room/locker_room_google_0196.jpg +meeting_room/conf12.jpg +nursery/image001.jpg +florist/scai_fev_07_9.jpg +closet/closet4.jpg +hospitalroom/blueberry_hill_blog_5_4.jpg +inside_bus/inside_bus_001.jpg +winecellar/cave_vin_31_17_altavista.jpg +library/librairie_16.jpg +jewelleryshop/joyeria_24_04_altavista.jpg +clothingstore/boutique777.jpg +grocerystore/idd_supermarche.jpg +inside_bus/inside_bus_083.jpg +pantry/pantry_85_24_flickr.jpg +bar/bar_0564.jpg +florist/c04652600522c7ad139144ca0e2b_2_4.jpg +prisoncell/jail1.jpg +shoeshop/lake_garda_shopping_shoe_shop.jpg +florist/florist_38_20_altavista.jpg +meeting_room/conf26.jpg +museum/museo_32_10_flickr.jpg +clothingstore/MashStorecopy.jpg +fastfood_restaurant/Carl_s_Jr_Green_Burrito_counter_in_SSF.jpg +auditorium/flunoauditorium5_1__54.jpg +meeting_room/113_bis_meeting_room.jpg +fastfood_restaurant/DSC06236.jpg +subway/subway_0268.jpg +classroom/american_history_classroom.jpg +kitchen/int396.jpg +meeting_room/Tuinh_Int_01.jpg +videostore/come_in.jpg +hairsalon/thumb.jpg +kitchen/indoor_0460.jpg +laboratorywet/peterfeinhood.jpg +deli/deli_120_05_flickr.jpg +operating_room/operating_room_05_16_altavista.jpg +tv_studio/studio02_lg_100_.jpg +grocerystore/800px_SuperstoreWinkler3.jpg +inside_subway/inside_subway_0337.jpg +classroom/90468092_47c4b7f04d.jpg +corridor/img_7677.jpg +prisoncell/jail_cell_3.jpg +auditorium/locaux_amphi_1_1__156.jpg +kindergarden/preschool23.jpg +closet/PeterCloset1edit.jpg +restaurant/restau_11.jpg +pantry/pantry_26_07_flickr.jpg +bedroom/indoor_0405.jpg +clothingstore/kleidung600.jpg +fastfood_restaurant/p374062_Fajardo_El_Pollo_Tropical.jpg +corridor/c16.jpg +warehouse/warehouse_0382.jpg +kindergarden/PreschoolClassroom2.jpg +fastfood_restaurant/cam2_big.jpg +bookstore/Lehmann_Buchhandlung_neu.jpg +garage/IMG_0778.jpg +children_room/100_3520_5_.jpg +garage/mso1011_ShelvesBike_aft1_w609.jpg +bowling/bowling_0071.jpg +computerroom/_7BB05260_2CB9_464F_8403_ABFA52061E3E_.jpg +jewelleryshop/joyeria21.jpg +concert_hall/CH_intro.jpg +dentaloffice/dentista_06_15_altavista.jpg +livingroom/n457042.jpg +dining_room/dining012.jpg +office/webP1010001.jpg +buffet/411053258_31173c5bea.jpg +elevator/elevator_google_0064.jpg +gameroom/sala_de_juegos_03_06_altavista.jpg +gym/1156_3.jpg +jewelleryshop/joyeria1.jpg +livingroom/at_01_5a_1604_05_l.jpg +cloister/oman_cloister.jpg +elevator/elevator_google_0070.jpg +trainstation/estacion_de_ferrocarriles_40_03_altavista.jpg +bedroom/indoor_0154.jpg +corridor/room113.jpg +videostore/blockbuster_28_11_flickr.jpg +mall/800px_Ueno_station_shopping_mall.jpg +airport_inside/airport_inside_0389.jpg +museum/museo_158_06_flickr.jpg +inside_subway/inside_subway_0364.jpg +locker_room/locker_room_google_0147.jpg +poolinside/0310_piscines_10.jpg +corridor/n457059.jpg +concert_hall/Essen_Philharmonie_594.jpg +dentaloffice/dentaire_12_09_flickr.jpg +movietheater/movietheater_google_0036.jpg +inside_subway/inside_subway_0110.jpg +laundromat/maschine_gross.jpg +library/bibliotheksaq.jpg +laundromat/how_to_organize_a_laundry_room_1.jpg +kindergarden/ClassroomLarge_gif.jpg +children_room/img_3971_25_.jpg +deli/deli_05_17_yahoo.jpg +bookstore/bookstore_01_09_flickr.jpg +kitchen/indoor_0250.jpg +artstudio/artist_studio_50_13_altavista.jpg +bookstore/bookstore_25_17_flickr.jpg +bedroom/room33.jpg +library/bibliotheque_400px.jpg +movietheater/movietheater_google_0028.jpg +church_inside/Westminster_Presbyterian_Church_Interior.jpg +inside_subway/inside_subway_0061.jpg +office/homeoff009.jpg +artstudio/art_painting_studio_30_13_altavista.jpg +bedroom/b28.jpg +deli/deli_03_03_altavista.jpg +museum/museum_36_07_altavista.jpg +hospitalroom/DSC_0035.jpg +library/Library_Pictures_3_.jpg +laundromat/VA_02_01_6305_27_l.jpg +livingroom/room408.jpg +bowling/bowling_0105.jpg +inside_bus/inside_bus_058.jpg +meeting_room/c1.jpg +prisoncell/prison_cell_22_18_altavista.jpg +pantry/pantry_144_18_flickr.jpg +toystore/spider_knife_low.jpg +lobby/lobby23.jpg +pantry/pantry_87_04_flickr.jpg +toystore/toys_store_23_20_altavista.jpg +mall/TheMallEngland.jpg +office/indoor_0148.jpg +gameroom/GameRoom45.jpg +operating_room/surgery_room_16_16_altavista.jpg +gym/uploads_images_photos_images_fullsize_gym.jpg +lobby/recibidor2.jpg +tv_studio/tvseriouscam2_540x404_119_.jpg +kitchen/indoor_0464.jpg +closet/banner2.jpg +bathroom/img_1115.jpg +toystore/tienda1.jpg +church_inside/metropolitana_123_11_flickr.jpg +fastfood_restaurant/connies_gif.jpg +kitchen/indoor_0439.jpg +bedroom/indoor_0332.jpg +trainstation/estacion_de_ferrocarriles_05_08_altavista.jpg +jewelleryshop/us1_54_.jpg +movietheater/movietheater_google_0050.jpg +church_inside/metropolitana_42_03_flickr.jpg +operating_room/surgery_room_15_08_altavista.jpg +airport_inside/airport_inside_0140.jpg +elevator/elevator_google_0098.jpg +hairsalon/salon03.jpg +movietheater/la_salle_1__52.jpg +trainstation/estacion_de_ferrocarriles_19_16_altavista.jpg +bowling/bowling_0117.jpg +casino/casino_0390.jpg +elevator/elevator_google_0049.jpg +locker_room/locker_room_google_0047.jpg +bedroom/indoor_0401.jpg +library/bibliotheque_photo.jpg +clothingstore/boutique_02_g.jpg +computerroom/laboratoire_informatique_bibliotheque.jpg +children_room/playroom11_55_.jpg +stairscase/S25.jpg +tv_studio/tv_studio_1b_70_.jpg +bar/bar_0308.jpg +subway/subway_0496.jpg +bakery/boulangerie_10_08_altavista.jpg +bakery/new_bakery_12_12_altavista.jpg +bowling/bowling_0085.jpg +garage/Garage77.jpg +deli/deli_09_06_yahoo.jpg +lobby/rectorate_lobby.jpg +pantry/pantry_29_19_flickr.jpg +pantry/larder.jpg +dentaloffice/dentista_124_14_flickr.jpg +restaurant_kitchen/restaurant_kitchen_google_0012.jpg +winecellar/wine_cellar_24_03_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0088.jpg +videostore/jobeth2.jpg +dentaloffice/dental_office_05_02_altavista.jpg +livingroom/room140.jpg +deli/deli_15_19_altavista.jpg +fastfood_restaurant/866_camilles_3_adamo_standalone_prod_affiliate_25.jpg +warehouse/warehouse_0295.jpg +bakery/best_bakery_01_17_altavista.jpg +prisoncell/prison_cell_01_20_flickr.jpg +mall/p312066_PATIO_OLMOS_SHOPPING_CENTER.jpg +bathroom/b9.jpg +greenhouse/Greenhouse632.jpg +greenhouse/hortela_nas_estufas_2.jpg +toystore/toys_store_35_03_altavista.jpg +warehouse/warehouse_0483.jpg +meeting_room/smallroom2W625.jpg +restaurant/pk_cella_bistro_1.jpg +restaurant_kitchen/restaurant_kitchen_google_0096.jpg +airport_inside/airport_inside_0425.jpg +corridor/n457056.jpg +meeting_room/conference5.jpg +movietheater/3_cin_1_1__11.jpg +dentaloffice/dental_office_01_08_altavista.jpg +laundromat/Waschsalon_05.jpg +nursery/baby_room.jpg +auditorium/the_auditorium_the_speakers_table_big_2104194540_1__73.jpg +gym/gimnasio_56_19_flickr.jpg +studiomusic/CzaplaMusicStudio_1.jpg +library/ins20.jpg +bar/bar_0349.jpg +gym/imgP2090065.jpg +florist/callas_floristeria.jpg +bookstore/PublicP131C_L.jpg +movietheater/cine0_1__43.jpg +grocerystore/store_400.jpg +livingroom/indoor_0483.jpg +hospitalroom/Afghan_Hospital_Room2_sized.jpg +grocerystore/moody_grocery_isle.jpg +pantry/WH_Pantry_WI.jpg +movietheater/petit_melies_1__56.jpg +dentaloffice/dentista_92_14_flickr.jpg +studiomusic/control5g.jpg +bakery/best_bakery_11_14_altavista.jpg +buffet/food_large_dessert.jpg +garage/Garage3.jpg +restaurant_kitchen/restaurant_kitchen_google_0081.jpg +studiomusic/studio_overall.jpg +locker_room/locker_room_google_0077.jpg +subway/subway_0369.jpg +livingroom/roomscan9.jpg +bowling/bowling_0025.jpg +children_room/VR_06_01_1001_06_l.jpg +shoeshop/taller11.jpg +stairscase/HO_06_04_4000_07_l.jpg +bakery/panaderia_39_02_altavista.jpg +florist/MVC_107S.jpg +kitchen/indoor_0468.jpg +bookstore/Librairie_07_08_altavista.jpg +greenhouse/greenhouse203.jpg +museum/museum_61_18_flickr.jpg +nursery/nursery_rhymes_wall_mural_main.jpg +museum/museum_20_09_altavista.jpg +bar/bar_0135.jpg +subway/subway_0014.jpg +bathroom/pasadena_IMG_0153.jpg +bedroom/n190068.jpg +cloister/Cloister_of_the_monastery_Unser_Lieben_Frauen_Magdeburg.jpg +prisoncell/prison_cell_05_11_altavista.jpg +garage/InsideGarDoor.jpg +gym/Weight_Room.jpg +hairsalon/home_cover_01.jpg +prisoncell/prison_cell_16_03_altavista.jpg +bedroom/roomscan37.jpg +clothingstore/images_bookstore_main1.jpg +cloister/DurhamCloister.jpg +bathroom/img_0646.jpg +inside_bus/inside_bus_025.jpg +airport_inside/airport_inside_0352.jpg +livingroom/indoor_0359.jpg +lobby/LV_04_01_0002_14_l.jpg +grocerystore/IMG_3103.jpg +jewelleryshop/fine_diamond_jeweler_35_.jpg +subway/subway_0420.jpg +bakery/panaderia_29_19_yahoo.jpg +prisoncell/prison_cell_03_11_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0031.jpg +casino/casino_0323.jpg +library/Off_Library.jpg +trainstation/train_station_03_14_altavista.jpg +bookstore/bookstore_10_23_flickr.jpg +inside_bus/inside_bus_042.jpg +locker_room/locker_room_google_0014.jpg +concert_hall/g_vigoenfotos_1961d.jpg +hospitalroom/DSC02829.jpg +inside_subway/inside_subway_0370.jpg +dentaloffice/dentista_09_24_flickr.jpg +restaurant/restaurante_157_21_flickr.jpg +videostore/lifeblog13122007004.jpg +studiomusic/1175259599.jpg +waitingroom/Studio.jpg +jewelleryshop/joyeria_08_18_flickr.jpg +winecellar/wine_cellar_33_15_altavista.jpg +church_inside/metropolitana_116_22_flickr.jpg +movietheater/dsc00021_1__50.jpg +elevator/elevator_google_0044.jpg +locker_room/locker_room_google_0138.jpg +shoeshop/bottom.jpg +artstudio/painters_studio_43_09_altavista.jpg +elevator/elevator_google_0004.jpg +hairsalon/shop.jpg +inside_bus/inside_bus_063.jpg +bookstore/bookstore_32_10_flickr.jpg +meeting_room/n457098.jpg +locker_room/locker_room_google_0162.jpg +airport_inside/airport_inside_0507.jpg +dining_room/dining046.jpg +gym/gimnasio_09_11_altavista.jpg +hairsalon/3_bains_du_marais.jpg +classroom/salle_de_classe67.jpg +laundromat/laverie092007_011.jpg +greenhouse/DSC00024.jpg +classroom/salle1950bg.jpg +kindergarden/pre_school_015_137130855_std.jpg +laundromat/lavanderia_74_14_flickr.jpg +waitingroom/attente.jpg +clothingstore/boutique_header5.jpg +airport_inside/airport_inside_0165.jpg +kindergarden/pre1.jpg +toystore/jugueteria_14_06_flickr.jpg +airport_inside/airport_inside_0321.jpg +dining_room/casa2.jpg +grocerystore/bstone13.jpg +laboratorywet/wet_lab_37_19_altavista.jpg +dining_room/dining033.jpg +closet/WC_Tower.jpg +kitchen/indoor_0251.jpg +computerroom/computerR.jpg +florist/floristeria2.jpg +inside_bus/inside_bus_002.jpg +bedroom/indoor_0069.jpg +laundromat/lavanderia_45_07_flickr.jpg +restaurant_kitchen/restaurant_kitchen_google_0101.jpg +studiomusic/lateraltecnico.jpg +jewelleryshop/2641107.jpg +inside_bus/inside_bus_010.jpg +lobby/Lobbywithstatue.jpg +pantry/13.jpg +pantry/pantry_16_22_flickr.jpg +movietheater/cinema_1__48.jpg +poolinside/t_pool.jpg +dining_room/dining016.jpg +gameroom/LW3DHc.jpg +buffet/Wedding_Food_3_edited.jpg +prisoncell/27jail_slide01.jpg +winecellar/cellar_design_svcs.jpg +classroom/classroom3.jpg +laboratorywet/wet_lab_08_04_altavista.jpg +toystore/toys_store_41_20_altavista.jpg +bookstore/bookstore_16_04_altavista.jpg +operating_room/surgery_room_07_08_altavista.jpg +bar/bar_0491.jpg +artstudio/estudio_de_pintor_06_02_altavista.jpg +lobby/bpi_lobby1.jpg +airport_inside/airport_inside_0306.jpg +florist/floristeria61.jpg +restaurant_kitchen/restaurant_kitchen_google_0042.jpg +stairscase/N190075.jpg +auditorium/auditorium_400_1__93.jpg +grocerystore/orlando.jpg +inside_bus/inside_bus_020.jpg +office/room477.jpg +office/office21.jpg +restaurant_kitchen/restaurant_kitchen_google_0040.jpg +waitingroom/waiting_room_34_14_altavista.jpg +deli/deli_34_21_flickr.jpg +buffet/url.jpg +deli/deli_counter.jpg +inside_subway/inside_subway_0316.jpg +children_room/VR_06_02_2000_59_l.jpg +kitchen/aa014484.jpg +warehouse/warehouse_0256.jpg +restaurant/restaurant_10_14_altavista.jpg +prisoncell/prison_cell_01_16_altavista.jpg +church_inside/pantry_120_08_flickr.jpg +inside_bus/inside_bus_021.jpg +museum/museum_128_24_flickr.jpg +casino/casino_0138.jpg +cloister/Claustro_Plasencia.jpg +jewelleryshop/PYC_039_PRY_PRY_146-153_014_i.jpg +children_room/VA_03_00_0003_42_l.jpg +laundromat/West_Equip.jpg +shoeshop/zapateria_04_11_flickr.jpg +warehouse/warehouse_0255.jpg +buffet/114811570_481eefa09f.jpg +operating_room/surgery_room_34_16_altavista.jpg +poolinside/lidopool03.jpg +hairsalon/Salon_poste_coiffure2_grand.jpg +dining_room/easyst036.jpg +winecellar/bodega_11_23_flickr.jpg +gameroom/AT_98_2_1623_31_l.jpg +tv_studio/438771324_4e003a475b_24_.jpg +operating_room/surgery_room_49_20_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0070.jpg +airport_inside/airport_inside_0290.jpg +nursery/2007_05_30_nursery_flickrfinds7.jpg +shoeshop/Chaussures.jpg +videostore/videoteca_13_10_altavista.jpg +elevator/elevator_google_0088.jpg +elevator/elevator_google_0022.jpg +greenhouse/47_Invernadero_1.jpg +classroom/Im423_5.jpg +locker_room/locker_room_google_0246.jpg +movietheater/movietheater_google_0046.jpg +gameroom/BR_05_01_1000_10_l.jpg +tv_studio/thumbnail_studiocameras_norm_106_.jpg +inside_bus/inside_bus_030.jpg +winecellar/cave.jpg +jewelleryshop/vilanova_i_la_geltru_joieria_maestre.jpg +toystore/jugueteria_16_16_flickr.jpg +bedroom/indoor_0177.jpg +jewelleryshop/r.jpg +movietheater/movietheater_google_0029.jpg +locker_room/locker_room_google_0168.jpg +shoeshop/zapateria_08_09_flickr.jpg +auditorium/auditorium_465x309_1__127.jpg +bedroom/indoor_0418.jpg +concert_hall/09260032.jpg +warehouse/warehouse_0139.jpg +inside_bus/inside_bus_031.jpg +children_room/br3playroom_10_.jpg +restaurant_kitchen/restaurant_kitchen_google_0055.jpg +clothingstore/shop10.jpg +bathroom/indoor_0309.jpg +pantry/pantry_104_14_flickr.jpg +subway/subway_0137.jpg +hospitalroom/brady_hospital.jpg +dining_room/homeoff002.jpg +trainstation/estacion_de_ferrocarriles_29_04_altavista.jpg +poolinside/059088D.jpg +waitingroom/salle_attente1.jpg +airport_inside/airport_inside_0428.jpg +inside_subway/inside_subway_0052.jpg +hospitalroom/HospitalRoom2_big.jpg +laundromat/VA_02_04_7112_19_l.jpg +winecellar/bodega_114_20_flickr.jpg +dentaloffice/dentaire_41_16_altavista.jpg +stairscase/int221.jpg +church_inside/iglesia2.jpg +library/library04.jpg +poolinside/1996_Pool.jpg +artstudio/art_painting_studio_10_19_altavista.jpg +fastfood_restaurant/Img_2369a.jpg +hairsalon/salon44.jpg +kindergarden/kindergarten_ansicht1.jpg +artstudio/art_painting_studio_09_10_altavista.jpg +kindergarden/Classroom_pic_8_23_07_001.jpg +dining_room/easyst042.jpg +casino/casino_0416.jpg +closet/152t.jpg +hospitalroom/hospital_room_1_second_foto.jpg +nursery/africa_nursery.jpg +subway/subway_0135.jpg +artstudio/painters_studio_08_12_altavista.jpg +inside_bus/inside_bus_077.jpg +elevator/elevator_google_0101.jpg +trainstation/gare_32_16_flickr.jpg +bowling/bowling_0026.jpg +concert_hall/PetronasPhilharmonicConcertHall.jpg +kitchen/kitchen003.jpg +studiomusic/sala6.jpg +florist/16943_7438_5.jpg +airport_inside/airport_inside_0275.jpg +concert_hall/concertHall.jpg +dentaloffice/dentista_112_04_flickr.jpg +mall/Wafi_RamRaid_Hussein_GNreader.jpg +toystore/toys_store_26_12_altavista.jpg +laboratorywet/wet_lab_35_12_altavista.jpg +movietheater/salle2_1__67.jpg +movietheater/slide8_74.jpg +garage/garage_inside_desk.jpg +meeting_room/n457017.jpg +subway/subway_0339.jpg +subway/underground_57_11_flickr.jpg +videostore/videotheque_02_07_flickr.jpg +computerroom/comproom3lrg.jpg +church_inside/metropolitana_147_01_flickr.jpg +nursery/cool_nursery.jpg +laundromat/lavanderia_110_22_flickr.jpg +computerroom/TrainingRoom_2.jpg +concert_hall/GD4777628_4112.jpg +classroom/classroom_front.jpg +prisoncell/jail_cells_3_big.jpg +videostore/videoclub_03_02_flickr.jpg +meeting_room/salledereunion1.jpg +bar/bar_0330.jpg +church_inside/Iglesia_del_Carmen.jpg +trainstation/gare_101_12_flickr.jpg +mall/galleria1.jpg +concert_hall/Flagey_Auditorium.jpg +grocerystore/store_counter.jpg +bowling/bowling_0161.jpg +concert_hall/rh_photo02.jpg +livingroom/dsc3101.jpg +warehouse/warehouse_0110.jpg +bathroom/room30.jpg +nursery/baby_room1.jpg +buffet/285264036_2976da5ec8.jpg +laboratorywet/wet_lab_22_15_altavista.jpg +poolinside/piscina_cubierta_09_01_altavista.jpg +cloister/808076.jpg +church_inside/buda_eglise_1.jpg +dentaloffice/dental_office_15_11_altavista.jpg +inside_bus/inside_bus_102.jpg +restaurant/witches_bistro_in_benidorm_large04.jpg +auditorium/auditorium_2__122.jpg +kindergarden/DSC000562.jpg +children_room/playroom15_57_.jpg +locker_room/locker_room_google_0039.jpg +auditorium/3195_auditorium_1__8.jpg +laundromat/lavanderia_05_14_flickr.jpg +meeting_room/conf16.jpg +poolinside/pool_inside_31_06_altavista.jpg +prisoncell/2226542443_5481b41c51.jpg +winecellar/wine_cellar_49_05_altavista.jpg +office/int594.jpg +lobby/AppLogicbooker_004.jpg +videostore/vanguard.jpg +bowling/bowling_0195.jpg +fastfood_restaurant/DSC00477.jpg +buffet/buffet-food.jpg +casino/casino_0183.jpg +corridor/accessories24.jpg +pantry/despensa_134_18_flickr.jpg +closet/p3_a.jpg +deli/deli_56_24_flickr.jpg +airport_inside/airport_inside_0062.jpg +closet/url_1.jpg +bar/bar_0597.jpg +inside_subway/inside_subway_0279.jpg +mall/hillsdale_mall_4.jpg +videostore/video_store_49_20_altavista.jpg +pantry/WC_Pantry_Door.jpg +toystore/jugueteria_21_11_flickr.jpg +winecellar/cave_champagne_08_06_altavista.jpg +classroom/classroom06.jpg +museum/museo_133_19_flickr.jpg +videostore/video_shop.jpg +classroom/Classroom.jpg +florist/anais480.jpg +clothingstore/Loja_de_roupa_e_sapataria.jpg +warehouse/warehouse_0437.jpg +hairsalon/peluqueria01.jpg +livingroom/l1.jpg +subway/subway_0150.jpg +casino/casino_0409.jpg +casino/casino_0336.jpg +library/shakespearebookshop.jpg +movietheater/movietheater_google_0061.jpg +auditorium/parsons_20auditorium_1__66.jpg +greenhouse/site_estufa_04.jpg +restaurant/interior.jpg +dentaloffice/dentaire_13_14_altavista.jpg +hairsalon/hair_salon_brisbane.jpg +bedroom/indoor_0166.jpg +inside_subway/inside_subway_0126.jpg +bedroom/bed136.jpg +dentaloffice/dentista_06_12_altavista.jpg +corridor/IMG_1691.jpg +clothingstore/dhd_boutique_2.jpg +library/bibliothek_ParagraphContainerList_ParagraphContainer0_ParagraphList_0002_Image.jpg +meeting_room/c14.jpg +bowling/bowling_0046.jpg +shoeshop/2200865379_9c78486bcd.jpg +studiomusic/estudio43.jpg +airport_inside/airport_inside_0095.jpg +restaurant_kitchen/restaurant_kitchen_google_0098.jpg +tv_studio/tv_studio3221_109_.jpg +laundromat/laundromat122.jpg +waitingroom/waiting_room_34_10_altavista.jpg +lobby/lobby21.jpg +kitchen/k2.jpg +museum/museum_19_19_altavista.jpg +pantry/pantry456781.jpg +kitchen/dscf2952.jpg +laboratorywet/laboratorio_quimica_17_11_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0068.jpg +buffet/buffet95.jpg +corridor/room497.jpg +bowling/bowling_0113.jpg +shoeshop/zapateria_11_23_flickr.jpg +clothingstore/bookstore.jpg +bedroom/room35.jpg +gameroom/sala_de_juegos_24_14_altavista.jpg +office/00_17.jpg +closet/smallsp019.jpg +shoeshop/zapateria11.jpg +inside_subway/inside_subway_0179.jpg +toystore/jugueteria_07_08_yahoo.jpg +bar/bar_0125.jpg +clothingstore/CIMG0506.jpg +waitingroom/bookstore_sittingarea.jpg +garage/8751_12.jpg +children_room/playroom30_63_.jpg +bar/bar_0353.jpg +laboratorywet/wet_lab_28_11_altavista.jpg +pantry/pantry_97_14_flickr.jpg +tv_studio/tv_studio_13_08_altavista.jpg +clothingstore/michis_boutique.jpg +pantry/pantry_51_10_flickr.jpg +inside_subway/inside_subway_0069.jpg +kitchen/indoor_0438.jpg +pantry/elfa_reach_in_pantry_thumb.jpg +cloister/durham_cathedral_cloister_3_353x470.jpg +toystore/286605441_d9919621ce.jpg +winecellar/Wine_cellar.jpg +pantry/despensa_05_08_flickr.jpg +laundromat/VR_06_02_2000_89_l.jpg +dentaloffice/IMG_0087_1_.jpg +classroom/position_room.jpg +laboratorywet/wet_lab_2.jpg +hospitalroom/PICT8223.jpg +nursery/Chambre_enfant.jpg +bookstore/bookstore_19_16_altavista.jpg +gym/gimnasio_98_10_flickr.jpg +kindergarden/meeting_rug_windows_doors_large2.jpg +hairsalon/img125712.jpg +operating_room/operating_room_29_19_altavista.jpg +casino/casino_0375.jpg +office/homeoff004.jpg +corridor/c13.jpg +bookstore/Photo_Librairie_001_Small_.jpg +greenhouse/greenhouselk2.jpg +museum/museo_01_12_altavista.jpg +concert_hall/6.jpg +elevator/elevator_google_0018.jpg +warehouse/warehouse_0412.jpg +warehouse/warehouse_0189.jpg +restaurant/restaurante_27_10_altavista.jpg +waitingroom/105NC_New_Reception_area_on_7th_floor.jpg +museum/museum_36_08_altavista.jpg +locker_room/locker_room_google_0070.jpg +toystore/url.jpg +bakery/panaderia_06_14_yahoo.jpg +museum/museo_32_13_flickr.jpg +waitingroom/Waiting_Area2.jpg +gym/HO_00_01_5186_23_l.jpg +locker_room/locker_room_google_0012.jpg +stairscase/AT_04_05_4901_42_l.jpg +dentaloffice/dentista_74_09_flickr.jpg +warehouse/warehouse_0058.jpg +bathroom/room322.jpg +casino/casino_0453.jpg +bar/bar_0263.jpg +corridor/int74.jpg +trainstation/gare_116_03_flickr.jpg +nursery/photo_baby_nursery1_062203.jpg +children_room/083008_saturday_24__3_.jpg +jewelleryshop/610x2_11_.jpg +nursery/baby_nursery_decorating_ideas_8.jpg +kindergarden/classroom02.jpg +stairscase/N457076.jpg +restaurant/restaurant_16_03_altavista.jpg +videostore/VhsMachine.jpg +airport_inside/airport_inside_0044.jpg +casino/casino_0145.jpg +office/n457048.jpg +church_inside/2054074790031860892lbuUCO_ph.jpg +stairscase/escalier7.jpg +winecellar/cave_vin_48_06_altavista.jpg +buffet/944.jpg +garage/julian_garage.jpg +inside_bus/inside_bus_055.jpg +nursery/2006_09_21_nursery4.jpg +shoeshop/2046shoes2.jpg +livingroom/int123.jpg +shoeshop/32814.jpg +toystore/toys_store_23_02_altavista.jpg +bedroom/b1.jpg +laboratorywet/wet_lab_25_15_altavista.jpg +poolinside/Schwimmbad_badestube.jpg +greenhouse/greenhouse_l.jpg +laboratorywet/laboratorio_quimica_03_04_altavista.jpg +office/n457052.jpg +casino/casino_0134.jpg +poolinside/Branzez_new_bazen2a.jpg +closet/Maple_Walk_in_Closet.jpg +dining_room/d10.jpg +buffet/417654380_efa8dabfda.jpg +casino/casino_0367.jpg +meeting_room/int864.jpg +bathroom/bathroom2.jpg +livingroom/06_salon_y_cocina.jpg +bowling/bowling_0184.jpg +toystore/jugueteria_16_09_flickr.jpg +studiomusic/deep_studios_901.jpg +casino/casino_0374.jpg +clothingstore/r6yf3e33.jpg +airport_inside/airport_inside_0568.jpg +jewelleryshop/314097artemovilBig.jpg +stairscase/AT_99_4_7098_27_l.jpg +greenhouse/Pic_4748_33.jpg +meeting_room/n457012.jpg +museum/museum_43_05_altavista.jpg +warehouse/warehouse_0027.jpg +grocerystore/44l.jpg +laboratorywet/lab_testing_i1.jpg +museum/museo_158_08_flickr.jpg +buffet/buffet_galleryfull.jpg +inside_bus/inside_bus_084.jpg +cloister/462681141_71f2f52863.jpg +restaurant_kitchen/restaurant_kitchen_google_0004.jpg +greenhouse/AMFWTOaa.jpg +airport_inside/airport_inside_0088.jpg +warehouse/warehouse_0291.jpg +concert_hall/3673_Concert_Hall_interior_1_Nigel_Luckhurst_1.jpg +kitchen/indoor_0514.jpg +gameroom/GottliebCorner4.jpg +hairsalon/salon_front.jpg +subway/subway_0265.jpg +grocerystore/main.jpg +toystore/Spielzeug_168_24_flickr.jpg +bowling/bowling_0150.jpg +hairsalon/1117858298_3ddfab52d3.jpg +meeting_room/ins28.jpg +nursery/baby's_room.jpg.jpg +operating_room/operating_room_24_11_altavista.jpg +deli/deli_123_12_flickr.jpg +gameroom/doc0003.jpg +poolinside/piscina_cubierta_07_05_altavista.jpg +bowling/bowling_0072.jpg +buffet/buffet.jpg +bookstore/bookstore_48_01_flickr.jpg +artstudio/painters_studio_02_15_altavista.jpg +fastfood_restaurant/Godfathers_Knoxville_1.jpg +livingroom/indoor_0038.jpg +studiomusic/frente3.jpg +florist/floreria_05_02_flickr.jpg +stairscase/D26.jpg +gym/gimnasio_24_12_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0043.jpg +subway/subway_0495.jpg +trainstation/estacion_de_tren_07_13_altavista.jpg +garage/3_CAR_GARAGE_1_2_25782147_std.jpg +auditorium/auditorium_seats_1__98.jpg +computerroom/computer.jpg +mall/galleria.jpg +tv_studio/837109_174933784a_m_28_.jpg +bookstore/800px_Wil_Buchhandlung_3104.jpg +dining_room/indoor_0054.jpg +museum/museo_139_14_flickr.jpg +meeting_room/c_salle26.jpg +operating_room/surgery_room_03_18_altavista.jpg +shoeshop/zapateria_08_05_flickr.jpg +bowling/bowling_0127.jpg +cloister/Claustro_de_Sant_Benet_de_Bages.jpg +nursery/complete_baby_room_furnitures2.jpg +office/homeoff012.jpg +classroom/classroom10.jpg +restaurant_kitchen/restaurant_kitchen_google_0032.jpg +bookstore/bookstore_03_15_flickr.jpg +trainstation/estacion_de_ferrocarriles_28_19_altavista.jpg +computerroom/302496265_f7b9b7a91f.jpg +museum/museo_16_20_flickr.jpg +movietheater/2792925915_36557baec0_1__10.jpg +tv_studio/tv2_111_.jpg +bakery/boulangerie_15_05_altavista.jpg +airport_inside/airport_inside_0470.jpg +artstudio/artist_studio_50_02_altavista.jpg +computerroom/Vaololoa_Computer_Room.jpg +stairscase/N457078.jpg +bakery/famous_bakery_05_12_altavista.jpg +church_inside/metropolitana_69_04_flickr.jpg +studiomusic/int285.jpg +poolinside/room147.jpg +computerroom/computer_room500px.jpg +auditorium/salle_amphitheatre4_1__171.jpg +grocerystore/feijen_1.jpg +cloister/cloister_cc_redandgray.jpg +fastfood_restaurant/quiznos1b.jpg +office/homeoff003.jpg +stairscase/int839.jpg +concert_hall/russia_concert_hall1.jpg +bakery/panaderia_01_03_yahoo.jpg +deli/deli_165_16_flickr.jpg +jewelleryshop/joyeria_01.jpg +lobby/hotel_lobby_big.jpg +tv_studio/studio1_101_.jpg +bathroom/int17.jpg +cloister/cloister_lg.jpg +library/library.jpg +laundromat/lavanderia_74_21_flickr.jpg +office/IMG_1530.jpg +trainstation/gare_163_18_flickr.jpg +gym/1192_original_gym_photos_001.jpg +locker_room/locker_room_google_0008.jpg +kindergarden/Preschool_Class.jpg +operating_room/operating_room_12_03_altavista.jpg +children_room/playroom_261100837_std_29_.jpg +trainstation/estacion_de_ferrocarriles_46_05_altavista.jpg +tv_studio/plato_de_malaga_television_situada_en_el_parque_tecnologico_de_andalucia_3896_57_.jpg +children_room/021108_spasticroomjpg_1_.jpg +waitingroom/waiting_room_36_20_altavista.jpg +bathroom/room268.jpg +dining_room/d7.jpg +florist/Floristeria002.jpg +toystore/giocattolo_57_16_flickr.jpg +deli/deli_02_03_altavista.jpg +greenhouse/invernadero1112.jpg +deli/deli_40_24_flickr.jpg +laboratorywet/wet_lab_08_11_altavista.jpg +elevator/elevator_google_0051.jpg +bedroom/IMG_9810.jpg +florist/1124747997193_600x600.jpg +videostore/videotheque_02_02_flickr.jpg +bathroom/dsc01582.jpg +jewelleryshop/rlj_showroom_26_.jpg +museum/museum_22_20_altavista.jpg +operating_room/surgery_room_06_05_altavista.jpg +bowling/bowling_0018.jpg +concert_hall/2588262_5876a97acf.jpg +bowling/bowling_0044.jpg +corridor/couloir_sized.jpg +kindergarden/Classroom04.jpg +elevator/elevator_google_0030.jpg +kitchen/indoor_0259.jpg +laboratorywet/wet_lab_48_10_altavista.jpg +operating_room/surgery_room_51_07_altavista.jpg +jewelleryshop/jewelry_store_2_40_.jpg +bookstore/librairie2.jpg +classroom/classroom6.jpg +restaurant/restaurant_18_15_altavista.jpg +livingroom/living7.jpg +museum/museum_120_07_flickr.jpg +gameroom/AT_04_04_2000_36_l.jpg +operating_room/operating_room_08_18_altavista.jpg +grocerystore/grocery_store_empty_ezr.jpg +livingroom/indoor_0158.jpg +restaurant_kitchen/restaurant_kitchen_google_0028.jpg +buffet/tsn_buffet_table.jpg +warehouse/warehouse_0148.jpg +gameroom/gameroom78.jpg +bar/bar_0321.jpg +mall/url.jpg +corridor/hall135.jpg +inside_subway/inside_subway_0068.jpg +library/howland.jpg +kindergarden/playroom01.jpg +winecellar/wine_cellar_32_04_altavista.jpg +inside_subway/inside_subway_0039.jpg +deli/deli_23_18_flickr.jpg +museum/museum_21_10_altavista.jpg +cloister/Cloister_Jun04_DC4408sAR.jpg +computerroom/Salle_informatique.jpg +tv_studio/george_neta_studio_90_.jpg +stairscase/room45.jpg +lobby/lobby27.jpg +nursery/nursery2.jpg +mall/mall06.jpg +garage/35228685_image052a.jpg +bakery/panaderia_01_06_yahoo.jpg +trainstation/estacion_de_ferrocarriles_38_16_altavista.jpg +gym/gym_147_01_flickr.jpg +movietheater/auditorium712_39.jpg +casino/casino_0270.jpg +cloister/cloister8.jpg +bedroom/dsc_4440.jpg +poolinside/piscina_cubierta_10_11_altavista.jpg +computerroom/salle_informatique_hr.jpg +meeting_room/int677.jpg +church_inside/metropolitana_115_15_flickr.jpg +grocerystore/Image023.jpg +closet/St_Ann_s_Clothes_Closet.jpg +restaurant_kitchen/restaurant_kitchen_google_0025.jpg +bookstore/livres.jpg +grocerystore/023_supermarkt.jpg +winecellar/wine_cellar_21_02_altavista.jpg +bathroom/IMG_9647.jpg +children_room/playroom26_60_.jpg +kindergarden/DSC000502.jpg +prisoncell/Alcatraz_prison_cell.jpg +inside_bus/inside_bus_057.jpg +waitingroom/sala_de_espera_01_17_altavista.jpg +mall/mall_of_the_emirates.jpg +restaurant/restaurante_07_13_altavista.jpg +fastfood_restaurant/DSC00478.jpg +gameroom/gameroom98.jpg +library/danielkimberlylibrarycl1.jpg +toystore/jugueteria_21_01_flickr.jpg +livingroom/indoor_0327.jpg +toystore/Spielzeug_58_01_flickr.jpg +church_inside/_wsb_534x461_Kirche_innen_2.jpg +jewelleryshop/hols2008036.jpg +poolinside/47220_pool_inside4.jpg +children_room/cimg1971_11_.jpg +garage/Garage678.jpg +fastfood_restaurant/showcase_2_177.jpg +laboratorywet/wet_lab_12_07_altavista.jpg +bakery/bakery_07_20_yahoo.jpg +hospitalroom/habitacion_hospital_01_18_altavista.jpg +florist/florist_83_07_flickr.jpg +prisoncell/Jail40.jpg +office/office15.jpg +deli/Superquinn_Deli.jpg +museum/museo_138_15_flickr.jpg +corridor/c20.jpg +elevator/elevator_google_0093.jpg +closet/closet34.jpg +elevator/elevator_google_0094.jpg +garage/garage89.jpg +movietheater/salle_cinema_corte_1__63.jpg +kitchen/100_2855.jpg +videostore/blockbuster_22_08_flickr.jpg +bar/bar_0020.jpg +jewelleryshop/joyeria_163_02_flickr.jpg +videostore/videoteca_38_04_altavista.jpg +laboratorywet/wet_lab_05_10_altavista.jpg +shoeshop/211519.jpg +clothingstore/dd_streets3.jpg +florist/floreria_03_20_flickr.jpg +stairscase/int743.jpg +closet/master_closet56.jpg +livingroom/indoor_0466.jpg +restaurant/restaurant_31_03_altavista.jpg +bakery/new_bakery_02_08_altavista.jpg +children_room/VR_06_02_2000_70_l.jpg +grocerystore/Grocery_Store_2.jpg +restaurant_kitchen/restaurant_kitchen_google_0089.jpg +inside_bus/inside_bus_082.jpg +gym/gimnasio_24_15_altavista.jpg +studiomusic/22949091_692d02b8f1.jpg +cloister/claustro53.jpg +mall/mall48.jpg +greenhouse/site_estufa_02.jpg +laundromat/laverie0054.jpg +greenhouse/finished_inside.jpg +subway/subway_0167.jpg +prisoncell/Carcel_Prision_Break.jpg +concert_hall/EmersonConcertHall.jpg +bakery/bakery_01_06_yahoo.jpg +greenhouse/greenhouse2iu1.jpg +florist/florist_85_16_flickr.jpg +closet/N43m.jpg +laundromat/paseacoastwash.jpg +concert_hall/Concert_Hall_color.jpg +office/img_0012.jpg +stairscase/room44.jpg +trainstation/estacion_de_ferrocarriles_37_12_altavista.jpg +corridor/hall89.jpg +fastfood_restaurant/buffet2.jpg +trainstation/gare_161_05_flickr.jpg +library/room215.jpg +locker_room/locker_room_google_0075.jpg +shoeshop/zapato_3.jpg +deli/deli_34_06_altavista.jpg +kindergarden/S5030019.jpg +winecellar/wine_storage_35_15_altavista.jpg +poolinside/photo_spa_buddhabarevian.jpg +laboratorywet/laboratorio_quimica_07_02_altavista.jpg +meeting_room/Salle_reunion2.jpg +restaurant/restaurant_01_01_altavista.jpg +dentaloffice/dentaloffice03.jpg +hospitalroom/hospital_room_0.jpg +kitchen/indoor_0300.jpg +artstudio/art_painting_studio_05_14_altavista.jpg +greenhouse/greenhouse_pics_019.jpg +inside_subway/inside_subway_0264.jpg +classroom/400px_Walton_High_School_New_Classroom.jpg +fastfood_restaurant/fast_food_restaurant.jpg +waitingroom/sigmund_freud_museum11.jpg +restaurant_kitchen/restaurant_kitchen_google_0013.jpg +dentaloffice/dentaire_11_02_flickr.jpg +prisoncell/Jail_Cell_4.jpg +bar/bar_0040.jpg +museum/museo_149_24_flickr.jpg +studiomusic/estudioB01.jpg +cloister/2551810032_c75699b9dd.jpg +corridor/corridor3.jpg +bowling/bowling_0114.jpg +poolinside/Pool_inside2_1.jpg +tv_studio/muticamera_studio_set_93_.jpg +kitchen/room7.jpg +artstudio/artist_studio_50_15_altavista.jpg +buffet/1092889351_618b8c3798_o.jpg +prisoncell/prison_cell_38_05_altavista.jpg +bookstore/Libreria_15_06_altavista.jpg +gameroom/AT_99_4_7096_33_l.jpg +jewelleryshop/silverjewelleryshop1.jpg +mall/milano_galleria_statues.jpg +livingroom/n57.jpg +classroom/fouarre_classroom_02.jpg +subway/subway_0211.jpg +poolinside/0310_piscines_7.jpg +dining_room/dining004.jpg +restaurant/room257.jpg +videostore/blockbuster_40_01_flickr.jpg +waitingroom/JHC_3.jpg +hospitalroom/hospital_room02.jpg +videostore/web_japanese_video_store.jpg +restaurant_kitchen/restaurant_kitchen_google_0071.jpg +toystore/im001034.jpg +hairsalon/innen1.jpg +winecellar/bodega_123_16_flickr.jpg +restaurant_kitchen/restaurant_kitchen_google_0083.jpg +inside_subway/inside_subway_0024.jpg +gameroom/Salle_de_Jeux_Villa_3_1_.jpg +winecellar/cave_champagne_08_08_altavista.jpg +dining_room/easyst027.jpg +tv_studio/2090354493_0da1a70d82_b_14_.jpg +dining_room/easyst039.jpg +florist/florist_47_11_flickr.jpg +artstudio/artistic_studio_08_08_altavista.jpg +bowling/bowling_0063.jpg +nursery/nursery_front.jpg +grocerystore/market.jpg +elevator/elevator_google_0068.jpg +prisoncell/41935jOqn_w.jpg +cloister/Claustro32.jpg +nursery/201584pvxu_w.jpg +waitingroom/waitingroom580.jpg +toystore/jugueteria_05_02_flickr.jpg +florist/floreria_04_22_flickr.jpg +kindergarden/DSC000493.jpg +library/Bibliothek_im_Reformierten_Kollegium_Debrecen.jpg +locker_room/locker_room_google_0029.jpg +waitingroom/JHC_2.jpg +greenhouse/1412_mb_file_0a8c5_gif.jpg +auditorium/sbu_auditorium_1__172.jpg +bedroom/IMG_2429.jpg +hospitalroom/SuperStock_1560R2033878.jpg +bedroom/b20.jpg +dentaloffice/dentaire_24_16_altavista.jpg +operating_room/surgery_room_19_08_altavista.jpg +waitingroom/waiting_room_50_10_altavista.jpg +subway/subway_0491.jpg +tv_studio/artmediamuseum_79_.jpg +closet/closet31.jpg +waitingroom/waiting_room_28_18_altavista.jpg +computerroom/Computer_room_2.jpg +corridor/IMG_9659.jpg +computerroom/comp8.jpg +stairscase/int79.jpg +bookstore/Libreria_11_10_altavista.jpg +gym/gimnasio_54_12_flickr.jpg +clothingstore/magasin1_enfants.jpg +kindergarden/perdue_preschool.jpg +cloister/cloister5.jpg +mall/mallparadisevalley03.jpg +waitingroom/salle_d_attente.jpg +bathroom/indoor_0391.jpg +deli/deli_97_02_flickr.jpg +gameroom/_wsb_488x346_salle.jpg +museum/museo_155_18_flickr.jpg +waitingroom/salle_attente35.jpg +shoeshop/tienda2.jpg +elevator/elevator_google_0073.jpg +operating_room/surgery_room_02_19_altavista.jpg +church_inside/KircheMariannhillInnen.jpg +church_inside/HDRInderKirche_l.jpg +deli/deli_04_13_altavista.jpg +subway/subway_0313.jpg +cloister/Claustro_de_San_Juan_de_los_Reyes.jpg +garage/garage2.jpg +concert_hall/g_vigoenfotos_1936d.jpg +deli/deli_42_15_altavista.jpg +florist/florist_50_08_flickr.jpg +auditorium/hi_res_400x300_auditorium_s_1__148.jpg +studiomusic/studio2.jpg +library/Bibliothek1.jpg +bathroom/indoor_0416.jpg +corridor/n457040.jpg +dentaloffice/dentista_99_19_flickr.jpg +restaurant/restaurante_38_09_altavista.jpg +laundromat/laundry room.jpg +closet/WH_WI_with_Models.jpg +gym/gimnasio_10_01_altavista.jpg +artstudio/painters_studio_02_01_altavista.jpg +kindergarden/FSLO_1168018158_111158.jpg +livingroom/room131.jpg +operating_room/operating_room_16_10_altavista.jpg +stairscase/stairs08.jpg +cloister/04claustro.jpg +clothingstore/frenchconnection.jpg +locker_room/locker_room_google_0181.jpg +elevator/elevator_google_0021.jpg +winecellar/wine_cellar_32_09_altavista.jpg +lobby/Fairmont_Hotel_Lobby_PC_SF_CA.jpg +subway/subway_0371.jpg +lobby/sLobby19.jpg +gym/gimnasio_73_08_flickr.jpg +videostore/videoclub_07_10_flickr.jpg +bar/bar_0162.jpg +jewelleryshop/big_island_jewelers_ltd_4354_31_.jpg +kitchen/cdmc1151.jpg +lobby/27y5_1.jpg +winecellar/wine_cellar_05_15_altavista.jpg +winecellar/bodega_119_18_flickr.jpg +subway/subway_0381.jpg +mall/IMG_6449.jpg +nursery/_nana_s_corner_baby_s_room_2005_029.jpg +shoeshop/viv_29387_no.jpg +subway/subway_0035.jpg +waitingroom/egf_waitingroom.jpg +bookstore/bookstore_36_06_altavista.jpg +dining_room/int767.jpg +hospitalroom/Nathans_hospital_room.jpg +laboratorywet/Lab_3.jpg +casino/casino_0075.jpg +studiomusic/SynthesizerStudio.jpg +laundromat/lavanderia_79_21_flickr.jpg +hospitalroom/private_recovery_room.jpg +library/Library_Pictures.jpg +grocerystore/P1010024.jpg +poolinside/piscina_cubierta_03_10_altavista.jpg +children_room/_kids_rooms_38_.jpg +trainstation/train station_01_05_altavista.jpg +pantry/despensa_142_16_flickr.jpg +studiomusic/estudio02.jpg +livingroom/aa016553.jpg +gym/gimnasio_142_20_flickr.jpg +hairsalon/1870565281_d339778e97.jpg +hairsalon/area_belones_ella_el_peluqueria7.jpg +inside_subway/inside_subway_0426.jpg +artstudio/art_painting_studio_15_09_altavista.jpg +greenhouse/greenhouse123.jpg +casino/casino_0477.jpg +auditorium/auditorium_3__123.jpg +dining_room/dsc02285.jpg +lobby/Throne_Hall2.jpg +videostore/Curts001.jpg +office/img_0011.jpg +kitchen/indoor_0076.jpg +mall/Mall_of_America2.jpg +bathroom/indoor_0458.jpg +buffet/Buffet_Set_Up_2_gif.jpg +computerroom/Salle_informatiquewe.jpg +prisoncell/10e0gvm.jpg +tv_studio/a3tv_noticias_94_02_01_78_.jpg +warehouse/warehouse_0002.jpg +bathroom/indoor_0137.jpg +dining_room/easyst047.jpg +gameroom/OR_99_6_9534_25A_l.jpg +artstudio/artist_studio_33_01_altavista.jpg +laboratorywet/laboratorio_quimica_07_06_altavista.jpg +classroom/sala_convegni.jpg +corridor/aa053951.jpg +clothingstore/Dscf0007.jpg +hairsalon/fdc_2196_1.jpg +hospitalroom/hospital_room_10_09_altavista.jpg +lobby/lobby47.jpg +pantry/pantry_24_08_flickr.jpg +buffet/212297870_fbc8f52ba4.jpg +casino/casino_0391.jpg +office/ap_de_marlene035.jpg +meeting_room/Small_meeting_room.jpg +meeting_room/IDC_salle_de_reunion_01.jpg +lobby/lobby3423.jpg +bar/bar_0163.jpg +casino/casino_0161.jpg +inside_subway/inside_subway_0027.jpg +trainstation/train_station_06_17_altavista.jpg +classroom/web_classe.jpg +warehouse/warehouse_0495.jpg +elevator/elevator_google_0023.jpg +buffet/photo21_1.jpg +meeting_room/conf07.jpg +laboratorywet/laboratorio_quimica_10_05_altavista.jpg +hospitalroom/IMG_0453.jpg +winecellar/wine_cellar_25_02_altavista.jpg +bar/bar_0301.jpg +operating_room/operating_room_15_16_altavista.jpg +computerroom/COMPUTER_ROOM_1a.jpg +stairscase/bergot2.jpg +laundromat/lavanderia_04_11_flickr.jpg +closet/closet_systems.jpg +garage/623sw39ter_garage3.jpg +lobby/lobby02.jpg +bowling/bowling_0194.jpg +shoeshop/Interior_Marta_Corral.jpg +artstudio/painters_studio_02_03_altavista.jpg +laundromat/laundry_room_SHNS.jpg +airport_inside/airport_inside_0039.jpg +hospitalroom/habitacion_hospital_01_16_altavista.jpg +mall/atrio_home.jpg +greenhouse/Riverview08.jpg +tv_studio/room_g_59_.jpg +restaurant/Gaststatte_kl.jpg +hospitalroom/SL271112.jpg +bakery/famous_bakery_23_04_altavista.jpg +poolinside/47220_pool_mtn_patio_view2.jpg +warehouse/warehouse_0426.jpg +restaurant/restaurant_05_18_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0054.jpg +grocerystore/aislesjpg.jpg +auditorium/theatreroyalbristolauditorium_1__74.jpg +fastfood_restaurant/P1020181.jpg +subway/subway_0394.jpg +locker_room/locker_room_google_0151.jpg +studiomusic/ctr_scaled.jpg +tv_studio/egtv_estudiopg2_lo_43_.jpg +bar/bar_0484.jpg +church_inside/Kirche1.jpg +closet/closet36.jpg +trainstation/gare_130_20_flickr.jpg +hairsalon/salon_03.jpg +laboratorywet/Wet_Chem_Lab.jpg +stairscase/N190049.jpg +bakery/panaderia_05_20_yahoo.jpg +fastfood_restaurant/blimpie.jpg +bedroom/indoor_0246.jpg +mall/cover_home3.jpg +artstudio/painters_studio_08_10_altavista.jpg +bowling/bowling_0034.jpg +nursery/finished_babys_room__5_.jpg +church_inside/0706eglise_lg.jpg +dining_room/dining050.jpg +gameroom/HO_06_03_3000_63_l.jpg +gameroom/int30.jpg +shoeshop/101247.jpg +garage/garage-inside.jpg +pantry/pantry_124_03_flickr.jpg +toystore/281568964_bb524a3c9e_o.jpg +museum/museum_19_13_altavista.jpg +computerroom/1552211138_a17100bcf5.jpg +inside_subway/inside_subway_0212.jpg +restaurant/2006_11_tastingroom.jpg +greenhouse/greenhousegh1.jpg +bakery/bakery_17_09_yahoo.jpg +inside_subway/inside_subway_0017.jpg +prisoncell/cell.jpg +stairscase/escalera.jpg +dentaloffice/dental_office_20_06_altavista.jpg +florist/FLORISTERIA-CASAMITJANA-2.jpg +lobby/hotelobby222.jpg +bakery/boulangerie_25_20_yahoo.jpg +studiomusic/pic2.jpg +laboratorywet/senior_wet_lab_2.jpg +classroom/izieu_classe.jpg +auditorium/bernard_20tschumi_3_popup_1__47.jpg +classroom/salle_de_classe34.jpg +warehouse/warehouse_0268.jpg +bedroom/indoor_0210.jpg +library/370663920_b87c065936.jpg +trainstation/gare_72_23_flickr.jpg +concert_hall/Meng_Concert_Hall_.jpg +garage/garage654.jpg +fastfood_restaurant/fast_food_restaurants.jpg +laundromat/lavanderia_103_15_flickr.jpg +movietheater/sala_de_cine_04_20_altavista.jpg +movietheater/movietheater_google_0038.jpg +airport_inside/airport_inside_0003.jpg +trainstation/gare_75_17_flickr.jpg +hospitalroom/hospital_room_04_10_altavista.jpg +warehouse/warehouse_0065.jpg +inside_bus/inside_bus_096.jpg +auditorium/callway_auditorium_overview_500_x_345_1__49.jpg +artstudio/artstudio.jpg +clothingstore/0012.jpg +airport_inside/airport_inside_0152.jpg +locker_room/locker_room_google_0243.jpg +prisoncell/400_jailcell_doorview_070917_lasvegaspd.jpg +locker_room/locker_room_google_0063.jpg +corridor/c12.jpg +auditorium/auditorium_4__124.jpg +gameroom/Gameroom022.jpg +subway/metropolitana_106_01_flickr.jpg +warehouse/warehouse_0219.jpg +greenhouse/greenhousey_2.jpg +jewelleryshop/tienda.jpg +studiomusic/music_studio12.jpg +laboratorywet/laboratorio_quimica_14_14_altavista.jpg +bathroom/int483.jpg +elevator/elevator_google_0065.jpg +gameroom/salle_de_jeux56.jpg +gym/highfields_gym_420x315.jpg +waitingroom/waiting_room_15_20_altavista.jpg +garage/insidemahal03.jpg +casino/casino_0450.jpg +kitchen/indoor_0383.jpg +church_inside/4_9rieur_Le_Raincy_France_2007_interior.jpg +nursery/jungle_nursery3.jpg +shoeshop/2201663658_3ae8af1eac.jpg +toystore/IMG_0198_Asakusa_toy_store.jpg +inside_bus/inside_bus_049.jpg +closet/CherryCloset_Small_.jpg +bakery/bakery_19_05_yahoo.jpg +computerroom/00.jpg +church_inside/chiemsee_fraueninsel_kircheP9175239_gross.jpg +museum/museo_21_15_altavista.jpg +office/office7.jpg +artstudio/artist_studio_40_09_altavista.jpg +concert_hall/eej_concert_hall.jpg +deli/deli_68_04_flickr.jpg +restaurant/restaurante_51_04_altavista.jpg +auditorium/auditoriumherbauges_060042700_1223_28112007_1__137.jpg +livingroom/indoor_0021.jpg +locker_room/locker_room_google_0105.jpg +corridor/IMG_9660.jpg +studiomusic/int774.jpg +cloister/20188429.jpg +bookstore/Librairie_49_01_altavista.jpg \ No newline at end of file diff --git a/TrainImages.txt b/TrainImages.txt new file mode 100755 index 0000000..1ae64ef --- /dev/null +++ b/TrainImages.txt @@ -0,0 +1,5360 @@ +gameroom/bt_132294gameroom2.jpg +poolinside/inside_pool_and_hot_tub.jpg +winecellar/bodega_12_11_flickr.jpg +casino/casino_0512.jpg +livingroom/living58.jpg +mall/4984307.jpg +corridor/pasilltmpo_t.jpg +laboratorywet/laboratorio_quimica_07_05_altavista.jpg +bookstore/CIMG2743.jpg +casino/casino_0044.jpg +waitingroom/800px_VTBS_Waiting_room_of_Thai_Airways.jpg +clothingstore/c0011.jpg +garage/mso1012_FridgeBikes_aft_w609.jpg +prisoncell/territorial_prison_cell_block.jpg +tv_studio/tv_studio_11_13_altavista.jpg +inside_bus/inside_bus_024.jpg +laboratorywet/wet_lab_10_17_altavista.jpg +videostore/videoclub_07_11_flickr.jpg +grocerystore/Market5.jpg +inside_bus/inside_bus_078.jpg +bathroom/itoiletpaper.jpg +closet/458309322_42d901b9d8.jpg +laboratorywet/wet_lab_13_10_altavista.jpg +winecellar/cave_vin_10_10_altavista.jpg +jewelleryshop/Joyeria_Madrid_4.jpg +restaurant_kitchen/restaurant_kitchen_google_0103.jpg +airport_inside/airport_inside_0148.jpg +church_inside/CH_Horgen_RefKirche.jpg +library/Homework2.jpg +waitingroom/Waiting_Room_3.jpg +classroom/Kindergarden_classroom.jpg +laundromat/lavanderia_49_01_flickr.jpg +jewelleryshop/joyeria_44_18_altavista.jpg +closet/458309564_4e61036dc8.jpg +inside_subway/inside_subway_0305.jpg +winecellar/wine_cellar_24_10_altavista.jpg +gym/gym.jpg +fastfood_restaurant/DSC00472.jpg +warehouse/warehouse_0116.jpg +hospitalroom/hospitalRoom2.jpg +warehouse/warehouse_0241.jpg +trainstation/gare_117_20_flickr.jpg +dining_room/d5.jpg +greenhouse/paghmist.jpg +library/bibliothekfgd.jpg +bar/bar_0026.jpg +bookstore/photo_magasin.jpg +closet/closet6_lg.jpg +dentaloffice/dentista_48_04_altavista.jpg +florist/florist_40_10_altavista.jpg +subway/subway_0344.jpg +gym/hotel_megeve_11.jpg +bakery/new_bakery_34_18_altavista.jpg +children_room/img_0499_22_.jpg +computerroom/computer_room06.jpg +office/o4.jpg +waitingroom/sala_de_espera_01_14_altavista.jpg +hairsalon/_peluqueria2_w_gra.jpg +warehouse/warehouse_0041.jpg +toystore/jugueteria_08_09_yahoo.jpg +shoeshop/tienda_zapatos.jpg +stairscase/TA_99_3_0525_34_l.jpg +classroom/biblioteca2.jpg +movietheater/movietheater_google_0020.jpg +hairsalon/salon_karin_pc080158.jpg +greenhouse/051011Greenhouse.jpg +office/office19.jpg +bedroom/indoor_0194.jpg +laundromat/wasserette.jpg +studiomusic/08_alex_studio_shoot_053.jpg +videostore/410w.jpg +pantry/pantry_164_02_flickr.jpg +deli/deli_06_11_altavista.jpg +laboratorywet/dscn0387.jpg +laundromat/3349laundromat_01.jpg +airport_inside/airport_inside_0130.jpg +artstudio/artist_studio_33_14_altavista.jpg +bookstore/bookstore_03_09_flickr.jpg +bathroom/indoor_0404.jpg +toystore/1164876979_0.jpg +cloister/cloister66.jpg +operating_room/surgery_room_09_16_altavista.jpg +inside_subway/inside_subway_0378.jpg +bathroom/indoor_0323.jpg +inside_bus/inside_bus_029.jpg +clothingstore/indeximage.jpg +poolinside/0310_piscines_8.jpg +laboratorywet/wet_lab_38_18_altavista.jpg +prisoncell/prison_cell_22_19_altavista.jpg +toystore/InsideofStore.jpg +artstudio/artist_studio_50_19_altavista.jpg +gameroom/sallejeux567.jpg +warehouse/warehouse_0096.jpg +bedroom/hunter_room.jpg +cloister/Cloisterold.jpg +studiomusic/int772.jpg +dining_room/yellow_dining_room.jpg +dining_room/kitchen052.jpg +gameroom/Catskill_Hall_Recreational_Room.jpg +grocerystore/mod16b.jpg +trainstation/estacion_de_tren_08_03_altavista.jpg +bakery/best_bakery_08_01_altavista.jpg +hospitalroom/IMG_0124.jpg +tv_studio/studiocameras24_65_.jpg +hospitalroom/hosp_073.jpg +inside_bus/inside_bus_036.jpg +auditorium/spaauditorium2_1__69.jpg +pantry/pantry_106_24_flickr.jpg +pantry/pantry_28_01_flickr.jpg +airport_inside/airport_inside_0029.jpg +classroom/AULA11_1TAMAOGRANDE.jpg +clothingstore/kleidung01.jpg +bathroom/b11.jpg +pantry/pantry_43_24_flickr.jpg +closet/df_0_0.jpg +gym/gimnasio_48_07_altavista.jpg +kindergarden/preschool_large.jpg +poolinside/piscine_interieur.jpg +winecellar/ca_97_2_287_35a_l.jpg +inside_bus/inside_bus_045.jpg +toystore/toys_store_45_15_altavista.jpg +classroom/int116.jpg +deli/deli_158_04_flickr.jpg +office/estudio2.jpg +subway/metropolitana_24_08_altavista.jpg +hairsalon/hair.jpg +restaurant/restaurant_09_11_altavista.jpg +bar/bar_0171.jpg +operating_room/operating_room_03_18_altavista.jpg +laboratorywet/laboratorio_quimica_02_08_altavista.jpg +studiomusic/IglooMusicStudio2.jpg +closet/twincitiesclosets.jpg +bar/bar_0528.jpg +children_room/06playroom_2_.jpg +bedroom/room34.jpg +bar/bar_0430.jpg +concert_hall/theater.jpg +operating_room/operating_room_05_09_altavista.jpg +classroom/salle_classe_int.jpg +corridor/room513.jpg +hospitalroom/IMG_3470.jpg +bathroom/indoor_0452.jpg +dining_room/dining010.jpg +inside_bus/inside_bus_075.jpg +florist/florist_86_15_flickr.jpg +hospitalroom/Birthing_room_2.jpg +concert_hall/ConcertHallInterior.jpg +gym/SALLE3.jpg +auditorium/auditorium_side_view_134.jpg +inside_subway/inside_subway_0151.jpg +tv_studio/dsc02482_38_.jpg +toystore/Image13852843.jpg +classroom/img_0007.jpg +closet/Closet_Before_1_.jpg +dining_room/d15.jpg +computerroom/12_student_classroom_4_computer_training_1.jpg +studiomusic/estudio03.jpg +lobby/sLobby14.jpg +artstudio/art_painting_studio_19_07_altavista.jpg +auditorium/auditorium_6__126.jpg +bakery/panaderia_37_09_yahoo.jpg +kitchen/kitchen138.jpg +prisoncell/18_jail.jpg +elevator/elevator_google_0020.jpg +florist/floreria_07_08_flickr.jpg +inside_bus/inside_bus_014.jpg +garage/12_large.jpg +garage/GaragePOP_526x800.jpg +winecellar/bodega_36_10_yahoo.jpg +library/Bibliotheque.jpg +subway/subway_0334.jpg +gameroom/gameroom_tennis.jpg +clothingstore/arredo_negozio_abbigliamento.jpg +grocerystore/grocery_isle.jpg +gym/Gym1_png.jpg +casino/casino_0491.jpg +studiomusic/StudioA1.jpg +garage/ThierGarageStoreHomePage2.jpg +toystore/Spielzeug_101_24_flickr.jpg +grocerystore/bstone1.jpg +movietheater/movietheater_google_0007.jpg +prisoncell/carcel_carabanchel_04.jpg +auditorium/espace_du_centenaire_salle_de_conferences_spectacles_paris_12_auditorium_04_1__53.jpg +buffet/126829079_ec08a3996c.jpg +dentaloffice/dentista_70_19_flickr.jpg +bedroom/int107.jpg +corridor/pasillo_bajo_c.jpg +studiomusic/control_room_web.jpg +cloister/image.jpg +airport_inside/airport_inside_0309.jpg +closet/2007_09_07_nursery_WhiteelfaKidsReachInCloset.jpg +computerroom/Computer_room65.jpg +garage/basement_4.jpg +concert_hall/concert.hall.jpg +shoeshop/706140700191528.jpg +bedroom/bedroom14.jpg +bathroom/bath244.jpg +kindergarden/gingerroom1.jpg +dentaloffice/dentista_11_09_altavista.jpg +meeting_room/conf25.jpg +operating_room/surgery_room_01_18_altavista.jpg +poolinside/your_pool.jpg +library/publiclibrarypic.jpg +buffet/450px_Houseparty_buffet.jpg +concert_hall/400px_National_Theater_and_Concert_Hall_of_Taiwan.jpg +airport_inside/airport_inside_0246.jpg +operating_room/surgery_room_47_11_altavista.jpg +deli/deli_13_14_yahoo.jpg +kitchen/k3.jpg +children_room/imgp0662_20_.jpg +tv_studio/sandiego_newsteam_61_.jpg +office/room12.jpg +church_inside/metropolitana_136_23_flickr.jpg +clothingstore/MVC_013F.jpg +laundromat/PIC00033.jpg +gameroom/TA_99_2_0321_32_l.jpg +poolinside/Lis_3.jpg +jewelleryshop/tienda4.jpg +airport_inside/airport_inside_0449.jpg +bathroom/n190015.jpg +hairsalon/L_UniversDeL_Hom_01_photo_AG.jpg +laboratorywet/Tissue_culture.jpg +auditorium/photo_of_auditorium_1__160.jpg +closet/closet05.jpg +florist/3_1001.jpg +bookstore/librairie_Ermeton_sur_Biert_500_PIXELS.jpg +studiomusic/home.jpg +grocerystore/grocery1.jpg +deli/deli_97_23_flickr.jpg +bowling/bowling_0008.jpg +jewelleryshop/photo_hpf_boutique_kobe.jpg +inside_bus/inside_bus_097.jpg +concert_hall/jack_singer_concert_hall01.jpg +casino/casino_0133.jpg +airport_inside/airport_inside_0431.jpg +toystore/toys_store_06_15_altavista.jpg +waitingroom/waiting_room_36_14_altavista.jpg +corridor/room473.jpg +concert_hall/interiorconcert02.jpg +laboratorywet/wet_lab_08_05_altavista.jpg +restaurant/chambre_resto_2.jpg +hospitalroom/op_Room.jpg +videostore/mws_STOREINT.jpg +auditorium/mc2_auditorium_1__63.jpg +jewelleryshop/silver_jewellery_shop1.jpg +corridor/corridora4.jpg +gameroom/room381.jpg +bathroom/n190010.jpg +classroom/classroom_006.jpg +nursery/Chambre_Bebe_Babble_Circus.jpg +trainstation/gare_154_14_flickr.jpg +church_inside/friedrichswerdersche_kirche_eglise_friedrichswerder_03a.jpg +auditorium/kingsway_20auditorium_jpg_60.jpg +auditorium/dunning_20auditorium_52.jpg +waitingroom/400_Waiting_Room_2.jpg +bakery/best_bakery_12_16_altavista.jpg +airport_inside/airport_inside_0015.jpg +museum/museo_32_01_altavista.jpg +deli/deli_24_19_altavista.jpg +inside_subway/inside_subway_0194.jpg +warehouse/warehouse_0221.jpg +gameroom/OR_99_2_5387_32_l.jpg +kindergarden/image4.jpg +buffet/buffet5.jpg +fastfood_restaurant/resto.jpg +nursery/Chambre_Bebe_Kids_Gallery.jpg +deli/deli_128_19_flickr.jpg +computerroom/room201.jpg +laboratorywet/wet_lab_01_17_altavista.jpg +office/o9.jpg +garage/garage_103.jpg +hospitalroom/habitacion_hospital_07_11_altavista.jpg +livingroom/n457041.jpg +stairscase/S26.jpg +pantry/pantry_23_13_flickr.jpg +grocerystore/kasten_supermarkt_brouwers2_spar.jpg +operating_room/operating_room_13_03_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0063.jpg +classroom/dam_classroom4.jpg +hairsalon/DSC_0091.jpg +tv_studio/cd_teleideal_035_31_.jpg +waitingroom/waitingroom03.jpg +florist/florist2.jpg +museum/museo_106_06_flickr.jpg +church_inside/metropolitana_19_23_flickr.jpg +bookstore/ladder2.jpg +children_room/playroom_lg_35_.jpg +computerroom/informatica2.jpg +deli/deli_106_13_flickr.jpg +poolinside/30_x_50_Pool_Cover_Vertical_Inside_Shot.jpg +gameroom/FW_97_8_0587_31_l.jpg +hospitalroom/cama_hospital_09_09_altavista.jpg +bar/bar_0236.jpg +computerroom/749300760_2f7b1f5ccb.jpg +restaurant/restaurant_03_10_altavista.jpg +classroom/sala_aula.jpg +bedroom/roomscan36.jpg +gameroom/LV_04_02_0006_06_l.jpg +closet/816977_Orig.jpg +trainstation/int55.jpg +kitchen/cdmc1194.jpg +laboratorywet/laboratorio_quimica_04_03_altavista.jpg +grocerystore/CHG_GROCERY_2.jpg +jewelleryshop/store4_49_.jpg +poolinside/pool_inside_18_08_altavista.jpg +bookstore/Libreria_13_15_altavista.jpg +inside_bus/inside_bus_068.jpg +tv_studio/tv_studio324_116_.jpg +casino/casino_0372.jpg +laundromat/laundryroom02.jpg +grocerystore/318111272_14218344c8.jpg +operating_room/operating_room_34_08_altavista.jpg +bar/bar_0574.jpg +mall/West_End_Mall_3_DSC01801_m.jpg +greenhouse/dads_greenhouse.jpg +prisoncell/jail_cell2.jpg +elevator/elevator_google_0012.jpg +artstudio/artist_studio_47_03_altavista.jpg +clothingstore/boutique3.jpg +artstudio/art_painting_studio_04_10_altavista.jpg +classroom/Sala_de_Clases2.jpg +meeting_room/conference_room_1_lr_sm.jpg +greenhouse/greenhouse6789.jpg +prisoncell/scr_newjail1.jpg +waitingroom/Waiting-room.jpg +library/bibliothek1df.jpg +stairscase/Escalier_B1.jpg +elevator/elevator_google_0013.jpg +lobby/salones_04.jpg +deli/deli_62_12_flickr.jpg +corridor/slobby27.jpg +fastfood_restaurant/32170441.jpg +greenhouse/med_gallery_3601_179_581844.jpg +gym/herade_inside.jpg +bakery/bakery_16_12_yahoo.jpg +greenhouse/geranium.jpg +poolinside/pool_inside_23_18_altavista.jpg +bedroom/b24.jpg +lobby/lobby05.jpg +bar/bar_0118.jpg +cloister/claustro_romanico.jpg +dentaloffice/dentista_30_20_altavista.jpg +clothingstore/ins35.jpg +auditorium/auditori_1__91.jpg +winecellar/wine_cellar_floor_stone_gif.jpg +children_room/playroom28_61_.jpg +computerroom/ComputerClassroom1.jpg +casino/casino_0007.jpg +lobby/lobby17.jpg +livingroom/l14.jpg +prisoncell/carcel_143_15_flickr.jpg +computerroom/SalleInformatique45.jpg +casino/casino_0028.jpg +bakery/bakery.jpg +inside_bus/inside_bus_048.jpg +nursery/baby_bedding.jpg +lobby/AT_05_04_4000_59_l.jpg +poolinside/room153.jpg +artstudio/artist_studio_49_04_altavista.jpg +garage/mso901_1f_sportswallandlockersystem_w609.jpg +inside_bus/inside_bus_062.jpg +cloister/Claustro_de_la_catedral_de_Roda_de_Isabena.jpg +closet/showroom.jpg +garage/3009Garaje.jpg +movietheater/p1010969_1__27.jpg +poolinside/N190005.jpg +corridor/p1010080_c.jpg +garage/2006_09Action02.jpg +poolinside/pool_inside_34_04_altavista.jpg +movietheater/movietheater_google_0005.jpg +concert_hall/euph_soh_concert_hall_2300.jpg +prisoncell/jailcell6edited.jpg +artstudio/artist_studio_28_09_altavista.jpg +dentaloffice/dentista_74_08_flickr.jpg +trainstation/gare_137_21_flickr.jpg +computerroom/computer_lab.jpg +hospitalroom/hospital_room_10_01_altavista.jpg +tv_studio/plato2_96_.jpg +auditorium/grandauditorium_1__56.jpg +kindergarden/preschool_2.jpg +kitchen/k9.jpg +bowling/bowling_0010.jpg +mall/consumer1.jpg +dentaloffice/dentista_04_19_flickr.jpg +studiomusic/rspixstudio1.jpg +winecellar/bodega_48_09_flickr.jpg +deli/deli_158_17_flickr.jpg +clothingstore/Lulas_013.jpg +grocerystore/2058337_Shopping_for_fruit_in_the_grocery_store_0.jpg +warehouse/warehouse_0346.jpg +fastfood_restaurant/KFC_Interior_2.jpg +studiomusic/estudio_de_grabacion12.jpg +videostore/fall_profile11_312.jpg +auditorium/500x240_escalesaudito31211_1__12.jpg +cloister/cloister_wallpaper_1024x768.jpg +cloister/Cloister_Jun05_DC2822sAR800.jpg +buffet/528281656_34a35d85f2.jpg +grocerystore/152.jpg +gym/1_gymEmpty.jpg +lobby/LobbyWide.jpg +cloister/636059.jpg +operating_room/operating_room_01_18_altavista.jpg +classroom/aulainfantil.jpg +kindergarden/SA_3_4_PS_Classroom_pictures_011_2_.jpg +bowling/bowling_0207.jpg +waitingroom/waitroom127.jpg +mall/shopping mall.jpg +greenhouse/inverna_izq_full.jpg +grocerystore/f2b51ff9_67ac_4ffc_8efa_1355088ad5b6.jpg +hairsalon/Salon_innenaufnahme_03.jpg +poolinside/piscina_cubierta_01_01_flickr.jpg +shoeshop/zapateria_04_01_flickr.jpg +corridor/pasillo_1_c.jpg +cloister/202.jpg +auditorium/770248_942445_1__18.jpg +church_inside/kirche_innenu.jpg +warehouse/warehouse_0269.jpg +grocerystore/japanese_food_fruit_stand.jpg +restaurant/restaurant_37_11_altavista.jpg +subway/subway_0106.jpg +grocerystore/2479111961_003a15f0cf.jpg +jewelleryshop/Interior_1.jpg +restaurant_kitchen/restaurant_kitchen_google_0104.jpg +buffet/34583009_3e0d0befc2.jpg +dining_room/d14.jpg +shoeshop/zapato_4.jpg +bathroom/indoor_0278.jpg +gym/gym_16_01_altavista.jpg +church_inside/brenac01_eglise02.jpg +greenhouse/greenhouse_009.jpg +mall/larcomar_shopping.jpg +deli/new_deli_42_13_altavista.jpg +locker_room/locker_room_google_0173.jpg +computerroom/Salle_Multimedia.jpg +inside_subway/inside_subway_0408.jpg +meeting_room/c15.jpg +movietheater/auditorium799_40.jpg +meeting_room/conference_room_2.jpg +airport_inside/airport_inside_0344.jpg +laundromat/DSC03063.jpg +movietheater/img_1354_1__21.jpg +bowling/bowling_0052.jpg +grocerystore/DSC01468.jpg +airport_inside/airport_inside_0134.jpg +dentaloffice/dentista_91_20_flickr.jpg +florist/florist_08_13_altavista.jpg +shoeshop/zapateria_01_14_flickr.jpg +auditorium/audit400_1__89.jpg +elevator/elevator_google_0056.jpg +laundromat/Laundry_Room_bmp.jpg +bathroom/b12.jpg +winecellar/wine_cellar_18_11_altavista.jpg +elevator/elevator_google_0028.jpg +greenhouse/serre.jpg +bakery/new_bakery_02_20_altavista.jpg +meeting_room/n457009.jpg +shoeshop/shoes_shop_23_11_altavista.jpg +meeting_room/conf11.jpg +gym/gym_05_11_altavista.jpg +classroom/Osseo_classroom_flag.jpg +computerroom/recurso4.jpg +deli/deli_69_08_flickr.jpg +subway/subway_0437.jpg +operating_room/operating_room_01_08_altavista.jpg +children_room/playroom4_32_.jpg +pantry/pantry_51_16_flickr.jpg +poolinside/pool_inside_12_14_altavista.jpg +deli/new_deli_38_07_altavista.jpg +livingroom/room163.jpg +laundromat/laundry1.jpg +livingroom/int104.jpg +bar/bar_0369.jpg +mall/mall13.jpg +inside_bus/inside_bus_047.jpg +operating_room/surgery_room_48_15_altavista.jpg +casino/casino_0139.jpg +operating_room/surgery_room_15_06_altavista.jpg +church_inside/metropolitana_155_12_flickr.jpg +gym/gym5.jpg +jewelleryshop/non_art_side_jewelry_store_9_23_.jpg +inside_subway/inside_subway_0076.jpg +museum/museum_48_17_altavista.jpg +closet/MasterSuiteMapleLamWIBedrm.jpg +shoeshop/n301_image.jpg +stairscase/ins36.jpg +inside_subway/inside_subway_0085.jpg +movietheater/PPP0001908_P.jpg +studiomusic/262039342_29adefe21f.jpg +warehouse/warehouse_0091.jpg +grocerystore/cbra3.jpg +museum/museum_37_10_altavista.jpg +children_room/LV_04_04_0004_53_l.jpg +hairsalon/DVP4957863_P.jpg +fastfood_restaurant/ATCS_14_TacoBell.jpg +deli/new_deli_02_02_altavista.jpg +kindergarden/IMG_1643_35195531_std.jpg +lobby/Nef1.jpg +restaurant_kitchen/restaurant_kitchen_google_0082.jpg +bathroom/indoor_0491.jpg +operating_room/surgery_room_42_18_altavista.jpg +bathroom/dsc3101.jpg +florist/vente_de_fleurs_et_compositions_CAP1.jpg +church_inside/Chauvigny_Collegiale_Saint_Piere_IMG_4982.jpg +cloister/cloister_gladman.jpg +bedroom/indoor_0216.jpg +museum/museo_20_06_altavista.jpg +corridor/couloir_vestiaires_450.jpg +library/DSC02518.jpg +poolinside/pool_inside1.jpg +studiomusic/14770A04.jpg +trainstation/gare_109_17_flickr.jpg +bowling/bowling_0132.jpg +locker_room/locker_room_google_0176.jpg +bakery/bakery_07_18_yahoo.jpg +library/bibliothek_olbdw.jpg +artstudio/artistic_studio_08_13_altavista.jpg +mall/ins35.jpg +shoeshop/zapateria_09_02_flickr.jpg +laundromat/lavanderia_82_18_flickr.jpg +poolinside/piscina_cubierta_07_10_altavista.jpg +buffet/276833180_70e3e48991.jpg +concert_hall/xConcertHallF.jpg +church_inside/107_0781Wittenberg_Inside_SchlossKirche_1.jpg +cloister/IMG_0432.jpg +inside_bus/inside_bus_095.jpg +movietheater/20061119203235.jpg +deli/deli_68_10_flickr.jpg +library/BM_Frejus_Bibliotheque_1.jpg +locker_room/locker_room_google_0107.jpg +nursery/hddsn111_nurseryafter_w609.jpg +bar/bar_0141.jpg +elevator/elevator_google_0081.jpg +auditorium/presentation_20banque_20amphitheatre_1__164.jpg +nursery/cmag0308_babyrooms01.jpg +cloister/IMG_0425.jpg +operating_room/surgery_room_06_02_altavista.jpg +children_room/playroom_furniture_49_.jpg +cloister/2468653788_fb0b50ab2e.jpg +closet/main.jpg +kindergarden/Huntington_preschool_room.jpg +winecellar/bodega_50_04_yahoo.jpg +classroom/G28bsdc.jpg +children_room/playroom3_62_.jpg +dining_room/room4.jpg +gym/gimnasio_04_14_altavista.jpg +locker_room/locker_room_google_0209.jpg +concert_hall/concert_hall_photo.jpg +meeting_room/n457010.jpg +inside_bus/inside_bus_053.jpg +subway/subway_0330.jpg +waitingroom/waiting_room_10_05_altavista.jpg +movietheater/ZonasComunes122.jpg +closet/closet32.jpg +inside_bus/inside_bus_080.jpg +locker_room/locker_room_google_0087.jpg +closet/Haley_Custom_Closet_450.jpg +fastfood_restaurant/EnfieldTB.jpg +corridor/ins30.jpg +warehouse/warehouse_0406.jpg +prisoncell/prison-4.jpg +office/office_10.jpg +meeting_room/conference3.jpg +livingroom/living22.jpg +stairscase/int67.jpg +bakery/bakery_01_16_yahoo.jpg +fastfood_restaurant/Pizza_Hut_2.jpg +concert_hall/GrossesFsphs_gif.jpg +elevator/elevator_google_0052.jpg +subway/subway_0477.jpg +casino/casino_0036.jpg +kitchen/kitchen1a.jpg +library/library2.jpg +movietheater/img_entete.jpg +concert_hall/Grand_Theater_web.jpg +tv_studio/tv_studio_29_06_altavista.jpg +bedroom/b23.jpg +inside_subway/inside_subway_0272.jpg +meeting_room/conf23.jpg +operating_room/operating_room_02_02_altavista.jpg +classroom/reportage_322_4.jpg +dentaloffice/dentista_52_24_flickr.jpg +church_inside/33ChurchInterior.jpg +poolinside/RMC_Pool_picture_main_pool.jpg +kitchen/kitchen169.jpg +museum/museum_79_03_flickr.jpg +bar/bar_0310.jpg +toystore/jugueteria_10_17_yahoo.jpg +bar/bar_0196.jpg +bookstore/bookstore_17_13_altavista.jpg +corridor/p1010075_c.jpg +dining_room/d4.jpg +poolinside/piscina_cubierta_01_14_altavista.jpg +grocerystore/supermarche_2.jpg +fastfood_restaurant/CIMG0334_resize.jpg +buffet/food_table_3.jpg +bedroom/b18.jpg +inside_subway/inside_subway_0219.jpg +dentaloffice/dentista_114_20_flickr.jpg +toystore/Spielzeug_14_02_flickr.jpg +prisoncell/dp1795698.jpg +waitingroom/waiting_room_01_24_flickr.jpg +classroom/195707530_69d72677ab.jpg +prisoncell/cell0705.jpg +museum/museum_149_07_flickr.jpg +studiomusic/panoramica.jpg +locker_room/locker_room_google_0041.jpg +warehouse/warehouse_0001.jpg +inside_bus/inside_bus_037.jpg +nursery/pinkprincess_crib_full.jpg +office/room263.jpg +computerroom/ORDINATEUR2.jpg +livingroom/at_98_5_950_34_l.jpg +videostore/videotheque_01_02_flickr.jpg +fastfood_restaurant/mcdonalds.jpg +operating_room/operating_room_03_07_altavista.jpg +church_inside/metropolitana_79_07_flickr.jpg +elevator/elevator_google_0059.jpg +pantry/Picture_013_30581853_std.jpg +bathroom/indoor_0217.jpg +bowling/bowling_0191.jpg +computerroom/CompLab.jpg +artstudio/painters_studio_32_20_altavista.jpg +fastfood_restaurant/pizzahut_storefront0308.jpg +lobby/lobby20.jpg +bookstore/bookstore_51_10_altavista.jpg +cloister/Cloister_Monastery_Santa_Catalina_Arequipa_Peru.jpg +inside_bus/inside_bus_003.jpg +meeting_room/n457019.jpg +meeting_room/int754.jpg +bowling/bowling_0112.jpg +dentaloffice/dental_office_27_11_altavista.jpg +restaurant/restaurant_03_05_altavista.jpg +library/Bibliotheque_solvay_wall.jpg +greenhouse/greenhouse3.jpg +clothingstore/boutique8.jpg +garage/Green_Roof_Garage_Rob_and_Rowan.jpg +lobby/sLobby17.jpg +library/Library98.jpg +mall/ins34.jpg +kindergarden/classroom3.jpg +artstudio/art_painting_studio_10_11_altavista.jpg +closet/WH_WI_with_SuperSlide.jpg +bakery/panaderia_18_11_yahoo.jpg +bar/bar_0051.jpg +bathroom/b8.jpg +clothingstore/Bookstore88.jpg +library/130309783_f194f43f71.jpg +classroom/aulas_007.jpg +bar/bar_0006.jpg +inside_bus/inside_bus_044.jpg +artstudio/artist_studio_17_05_altavista.jpg +subway/subway_0028.jpg +florist/06_1.jpg +library/pano2.jpg +clothingstore/balboa_wet_seal_boutique.jpg +inside_bus/inside_bus_076.jpg +lobby/lobby04.jpg +auditorium/auditorium_web_2_1__100.jpg +hospitalroom/HospitalRoom3.jpg +nursery/abc_babylg.jpg +fastfood_restaurant/Papa_John_s_at_the_Al_Seef_Mall.jpg +laboratorywet/laboratorio_quimica_03_03_altavista.jpg +hairsalon/claudy_coiffure_salon_1.jpg +gameroom/OR_02_02_0480_29_l.jpg +dentaloffice/dentista_64_03_flickr.jpg +casino/casino_0045.jpg +closet/Master_Closet2.jpg +kitchen/dining047.jpg +bedroom/indoor_0434.jpg +buffet/hot_food.jpg +elevator/elevator_google_0032.jpg +winecellar/wine_cellar_03_10_altavista.jpg +bookstore/bookstore_05_14_altavista.jpg +concert_hall/BeallHall.jpg +restaurant/restaurante_04_18_altavista.jpg +garage/mso1012_LaundryTools_aft_w609.jpg +movietheater/movietheater_google_0031.jpg +poolinside/serv8_450.jpg +restaurant_kitchen/restaurant_kitchen_google_0046.jpg +casino/casino_0048.jpg +computerroom/url_gif.jpg +kitchen/cdmc1123.jpg +poolinside/P1050218.jpg +closet/10022250_2T.jpg +videostore/videoclub_14_15_altavista.jpg +elevator/elevator_google_0086.jpg +bookstore/bulles_vienne_04.jpg +buffet/P8311103.jpg +locker_room/locker_room_google_0102.jpg +cloister/SenanqueCloister.jpg +kindergarden/preschool_chairs_2006_3.jpg +laboratorywet/laboratorio_quimica_17_15_altavista.jpg +fastfood_restaurant/358x283.jpg +clothingstore/45_1343_Foto_nova_loja_Toulon_DSCN0669.jpg +livingroom/ph_01_06_87775_01_l.jpg +church_inside/kirche11.jpg +office/office3.jpg +restaurant/restaurant_19_19_altavista.jpg +dining_room/d12.jpg +garage/GarageInside_1.jpg +tv_studio/thumbnail_soundstage_norm_105_.jpg +museum/museo_161_19_flickr.jpg +restaurant_kitchen/restaurant_kitchen_google_0076.jpg +shoeshop/TIENDA.jpg +locker_room/locker_room_google_0185.jpg +hospitalroom/P1012467.jpg +winecellar/wine_cellar_44_03_altavista.jpg +prisoncell/2005_0809Image0027.jpg +library/Bibliothek.jpg +children_room/opt_playroom_tepee_47_.jpg +laboratorywet/laboratorio_quimica_18_12_altavista.jpg +pantry/pantry_42_04_flickr.jpg +buffet/528283746_b1dea3f70f.jpg +casino/casino_0340.jpg +gameroom/salle_de_jeux_08_09_altavista.jpg +movietheater/movietheater_google_0034.jpg +buffet/african_village_bakau_12.jpg +pantry/pantry_143_06_flickr.jpg +prisoncell/index_jail.jpg +bowling/bowling_0062.jpg +bowling/bowling_0170.jpg +casino/casino_0006.jpg +pantry/White_Pantry_System_Web_Size.jpg +poolinside/schwimmbad11_2.jpg +shoeshop/pht_mag_01.jpg +children_room/PO_06_03_3000_56_l.jpg +inside_bus/inside_bus_019.jpg +shoeshop/zapateria_23_21_flickr.jpg +elevator/elevator_google_0043.jpg +florist/florist_46_20_altavista.jpg +kitchen/kitchen174.jpg +kindergarden/preschool1.jpg +bathroom/cimg2046.jpg +church_inside/3_Le_Raincy_eglise_Notre_Dame_Vue_d_ensemble.jpg +closet/closets.jpg +toystore/262507104_bfa08ef897.jpg +laundromat/original.jpg +hairsalon/foa6agrande.jpg +movietheater/8cd35967ac80b2212d15ee90a06329ac_1__14.jpg +bowling/bowling_0037.jpg +movietheater/FindingNemo1_480x320.jpg +corridor/upper_main_hallway_1_c.jpg +gym/gimnasio_10_03_altavista.jpg +meeting_room/conf14.jpg +museum/museo_44_01_flickr.jpg +nursery/baby_room_011_738273.jpg +trainstation/gare_27_22_flickr.jpg +restaurant/restaurante_04_15_altavista.jpg +auditorium/global_millennium_prize_promotion_to_ricardo_palma_universitystudents_at_the_auditorium_df95e_1__55.jpg +deli/deli_40_11_altavista.jpg +dentaloffice/dentista_85_08_flickr.jpg +bookstore/Libreria_30_16_altavista.jpg +hairsalon/venta_405073650.jpg +restaurant/bistro_19_10_altavista.jpg +auditorium/amphi05_1__78.jpg +bowling/bowling_0013.jpg +casino/casino_0037.jpg +hospitalroom/hospital_room_04_13_altavista.jpg +children_room/playroom_0506_33_.jpg +florist/florist_26_19_altavista.jpg +hairsalon/photo_salon1.jpg +winecellar/wine_cellar_46_17_altavista.jpg +dining_room/kitchen064.jpg +gameroom/Salle_Jeux_58936.jpg +restaurant_kitchen/restaurant_kitchen_google_0030.jpg +gameroom/OGS_jeux_web.jpg +videostore/videotheque_01_23_flickr.jpg +inside_subway/inside_subway_0159.jpg +studiomusic/estudio01.jpg +auditorium/sp_auditorium_1907_3_1__175.jpg +bathroom/indoor_0498.jpg +classroom/sala02.jpg +restaurant_kitchen/restaurant_kitchen_google_0007.jpg +grocerystore/Oliera_Wijnegem_Shopping_Center.jpg +garage/hdts1013_Garage_bef_photo_w609.jpg +dentaloffice/dental_office_02_10_altavista.jpg +pantry/pantry_83_22_flickr.jpg +fastfood_restaurant/baja_fresh_restaurant_inside.jpg +mall/p95536_Minneapolis_Mall_of_America.jpg +pantry/pantry_120_01_flickr.jpg +dining_room/n190088.jpg +livingroom/int13.jpg +auditorium/amphitheatre987_25.jpg +cloister/build1383.jpg +museum/museo_07_01_flickr.jpg +library/473767793_d3cafc4eff.jpg +garage/UltiPro.jpg +library/Concord_Free_Public_Library_Renovation_122.jpg +livingroom/aa016556.jpg +children_room/ft_playroom01_l_43_.jpg +children_room/playroom10_54_.jpg +garage/garage_03uu.jpg +prisoncell/Mens_Jail.jpg +restaurant/room264.jpg +computerroom/1782001450_ec797a2c7c.jpg +poolinside/pool_inside_46_08_altavista.jpg +toystore/toys_store_03_20_altavista.jpg +closet/Walk_In_Closet_1.jpg +clothingstore/YogaBoutique.jpg +mall/eastridg1.jpg +bakery/panaderia_20_21_yahoo.jpg +bookstore/Librairie_07_07_altavista.jpg +waitingroom/PatientWaitingRoom.jpg +concert_hall/Atwood_bubble.jpg +waitingroom/a67092.jpg +gym/gym45.jpg +studiomusic/studiomusic.jpg +restaurant_kitchen/restaurant_kitchen_google_0065.jpg +auditorium/6a00d8341e38b153ef00e54f6a70a48834_500wi_1__15.jpg +inside_bus/inside_bus_094.jpg +laundromat/2067f.jpg +stairscase/room454.jpg +inside_subway/inside_subway_0291.jpg +waitingroom/cab3.jpg +auditorium/serre43_173.jpg +toystore/speelgoed_35_13_altavista.jpg +artstudio/artist_studio_36_05_altavista.jpg +office/ins4.jpg +auditorium/amphitheatre8_1__84.jpg +children_room/VA_04_02_6000_08_l.jpg +elevator/elevator_google_0096.jpg +library/Bibliothek_3_gr.jpg +jewelleryshop/duran_joyeros.jpg +bowling/bowling_0204.jpg +poolinside/pool_inside_40_20_altavista.jpg +computerroom/salle_info1.jpg +dentaloffice/dentaire_10_03_altavista.jpg +deli/deli_73_02_flickr.jpg +nursery/baby_room2.jpg +tv_studio/famie_studio_89_.jpg +poolinside/indooPool_Inside_gif.jpg +bakery/panaderia_49_01_altavista.jpg +stairscase/int667.jpg +artstudio/art_painting_studio_12_16_altavista.jpg +garage/garage_wall_strip_handled_500.jpg +greenhouse/redwhite.jpg +library/57048683_74701f9fa9.jpg +movietheater/movietheater_google_0015.jpg +restaurant/room251.jpg +tv_studio/tv_studio_3b_72_.jpg +warehouse/warehouse_0071.jpg +auditorium/auditorium001_102.jpg +livingroom/familyroom22.jpg +hospitalroom/HospitalRoom_big.jpg +locker_room/locker_room_google_0079.jpg +pantry/fp_closet_3.jpg +waitingroom/3multi1.jpg +jewelleryshop/452121570_c989f6e216.jpg +hospitalroom/IMG_0036.jpg +church_inside/P0000231.jpg +airport_inside/airport_inside_0162.jpg +subway/subway_0083.jpg +laboratorywet/wet_lab_09_02_altavista.jpg +mall/mall04.jpg +restaurant_kitchen/restaurant_kitchen_google_0020.jpg +movietheater/2234326815_058f93a495_1__6.jpg +mall/shopping_mall.jpg +cloister/claustro2.jpg +laboratorywet/laboratorio_quimica_06_04_altavista.jpg +prisoncell/46442359_cf7bc5c5d2.jpg +trainstation/train_station_07_07_altavista.jpg +artstudio/artist_studio_22_07_altavista.jpg +movietheater/movietheater_google_0009.jpg +grocerystore/Afbeelding_supermarkt_ergo.jpg +poolinside/pool_inside_08_07_altavista.jpg +winecellar/cave_champagne_21_15_altavista.jpg +operating_room/operating_room_08_09_altavista.jpg +prisoncell/policestationjailcell.jpg +subway/subway_0048.jpg +jewelleryshop/Silver_shop_in_Wua_Lai_Rd.jpg +airport_inside/airport_inside_0608.jpg +fastfood_restaurant/pizza_hut.jpg +operating_room/operating_room_33_02_altavista.jpg +classroom/rdg1.jpg +subway/subway_0349.jpg +hospitalroom/habitacion_hospital_01_05_flickr.jpg +church_inside/metropolitana_137_03_flickr.jpg +bakery/boulangerie_40_09_altavista.jpg +inside_subway/inside_subway_0261.jpg +waitingroom/waiting_ig.jpg +church_inside/KaGrou2.jpg +deli/deli_14_06_altavista.jpg +airport_inside/airport_inside_0535.jpg +florist/florist_40_09_altavista.jpg +operating_room/operating_room_02_19_altavista.jpg +deli/StoddartDeli1_1.jpg +waitingroom/photo_1241582.jpg +office/int281.jpg +warehouse/warehouse_0051.jpg +bathroom/room29.jpg +greenhouse/DSCN1239.jpg +gym/velocity_hoboken_gym.jpg +inside_subway/inside_subway_0398.jpg +poolinside/piscina_cubierta_06_04_altavista.jpg +prisoncell/prison_cell_22_10_altavista.jpg +laundromat/lavanderia_11_20_flickr.jpg +casino/casino_0266.jpg +closet/closet1.jpg +dining_room/room369.jpg +laboratorywet/laboratorio_quimica_08_13_altavista.jpg +dentaloffice/dentaire_44_12_altavista.jpg +bakery/boulangerie_24_19_altavista.jpg +buffet/P1011256_fs.jpg +winecellar/wine_cellar_04_06_altavista.jpg +poolinside/pool_inside_38_05_altavista.jpg +prisoncell/carcel_21_20_flickr.jpg +florist/44122_1.jpg +laboratorywet/wet_lab_03_14_altavista.jpg +concert_hall/Raoul_Jobin_07600017_200703_1.jpg +laundromat/lavanderia_01_08_altavista.jpg +elevator/elevator_google_0039.jpg +operating_room/surgery_room_02_07_altavista.jpg +bowling/bowling_0120.jpg +pantry/pantry_100_19_flickr.jpg +florist/florist_07_11_altavista.jpg +buffet/998866690_b2cc196ee1.jpg +meeting_room/conf17.jpg +bedroom/room359.jpg +winecellar/bodega_45_18_yahoo.jpg +classroom/sala01.jpg +poolinside/0310_piscines_6.jpg +buffet/food2.jpg +clothingstore/Bookstore_5.jpg +airport_inside/airport_inside_0409.jpg +lobby/url.jpg +dentaloffice/dentista_40_01_altavista.jpg +greenhouse/dscn0088.jpg +hospitalroom/hospital_room_07_17_altavista.jpg +lobby/LV_00_07_4491_02A_l.jpg +office/office16.jpg +trainstation/estacion_de_ferrocarriles_44_06_altavista.jpg +mall/ar118983325881591.jpg +auditorium/salle_auditorium_valenciennes_1__169.jpg +bowling/bowling_0074.jpg +computerroom/computer_room_jpg.jpg +inside_subway/inside_subway_0431.jpg +waitingroom/sala_de_espera_03_14_altavista.jpg +bakery/panetteria_03_12_altavista.jpg +airport_inside/airport_inside_0057.jpg +bathroom/indoor_0433.jpg +bar/bar_0254.jpg +computerroom/computer_2520classroom.jpg +casino/casino_0338.jpg +greenhouse/invernadero04.jpg +toystore/Spielzeug_24_11_altavista.jpg +trainstation/estacion_de_ferrocarriles_16_16_altavista.jpg +bedroom/int452.jpg +bookstore/Buchhandlung_Ansichten_innen5_150dpi.jpg +gym/gimnasio_68_21_flickr.jpg +bathroom/indoor_0196.jpg +library/scotland_library2_png.jpg +inside_subway/inside_subway_0421.jpg +florist/rt_wall.jpg +casino/casino_0261.jpg +trainstation/gare_100_06_flickr.jpg +hospitalroom/Baby063.jpg +waitingroom/recept2.jpg +bedroom/indoor_0584.jpg +pantry/Cognac_Pantry_wFull_Doors_Bskts_150.jpg +warehouse/warehouse_0381.jpg +gameroom/AT_98_4_1270_26_l.jpg +kindergarden/classroom_large2.jpg +pantry/agm_pantry_melamine.jpg +restaurant_kitchen/restaurant_kitchen_google_0009.jpg +corridor/p1010064_b.jpg +clothingstore/274757141_small.jpg +kitchen/kitchen265.jpg +trainstation/train_station_42_09_altavista.jpg +office/emba_offices_before_move.jpg +operating_room/surgery_room_22_09_altavista.jpg +casino/casino_0014.jpg +kitchen/kitchen65.jpg +pantry/42_17956239.jpg +cloister/852054.jpg +jewelleryshop/b102.jpg +airport_inside/airport_inside_0197.jpg +casino/casino_0038.jpg +meeting_room/n457003.jpg +bar/bar_0161.jpg +children_room/AT_04_02_3000_58_l.jpg +hairsalon/HPIM0491.jpg +classroom/salle_2.jpg +clothingstore/MelangeStoreInside.jpg +nursery/p1000855_1.jpg +tv_studio/grimshaw_photo7_91_.jpg +locker_room/locker_room_google_0154.jpg +airport_inside/airport_inside_0050.jpg +trainstation/estacion_de_ferrocarriles_30_01_altavista.jpg +office/o5.jpg +bookstore/Librairie_Licap1.jpg +tv_studio/3_estudio_de_television_1_23_.jpg +bowling/bowling_0081.jpg +deli/deli_142_15_flickr.jpg +kindergarden/IMG_1073.jpg +gym/gimnasio_28_20_flickr.jpg +kitchen/cdmc1145.jpg +operating_room/surgery_room_16_10_altavista.jpg +fastfood_restaurant/cam1_big.jpg +bowling/bowling_0124.jpg +subway/subway_0382.jpg +corridor/dsc00239.jpg +warehouse/warehouse_0094.jpg +church_inside/Random_church_interior.jpg +inside_subway/metropolitana_19_12_flickr.jpg +tv_studio/tv_studio_17_13_altavista.jpg +classroom/image17.jpg +inside_subway/inside_subway_0283.jpg +artstudio/art_painting_studio_10_12_altavista.jpg +buffet/616739082_d546057033.jpg +bakery/boulangerie_16_09_yahoo.jpg +computerroom/salle_matos.jpg +elevator/elevator_google_0092.jpg +greenhouse/Estufa_178.jpg +bookstore/bookstore_02_08_altavista.jpg +church_inside/metropolitana_68_11_flickr.jpg +laundromat/laundromat78.jpg +waitingroom/art2217_2.jpg +toystore/toys_store_22_11_altavista.jpg +tv_studio/estudiodetv_88_.jpg +bookstore/9f63b46ad67dbc0868afa77a45d339bb.jpg +kindergarden/DSC000522.jpg +elevator/elevator_google_0002.jpg +garage/623sw39ter_garage2.jpg +airport_inside/airport_inside_0330.jpg +kindergarden/Kindergarten_classroom.jpg +trainstation/gare_63_05_flickr.jpg +prisoncell/IMG_3782Copying.jpg +jewelleryshop/vase2_55_.jpg +shoeshop/75421439_6007807504.jpg +cloister/cloister3.jpg +stairscase/AT_97_4_396_33_l.jpg +greenhouse/53_3.jpg +toystore/262508526_93634b3c2c.jpg +bookstore/Libreria_34_13_altavista.jpg +casino/casino_0008.jpg +children_room/playroom_july_68_.jpg +greenhouse/greenhouse9.jpg +studiomusic/studio87.jpg +artstudio/estudio_de_pintor_07_08_altavista.jpg +bathroom/IMG_2437.jpg +airport_inside/airport_inside_0127.jpg +computerroom/Salle_Informatique_Bellavista_060831_2_.jpg +livingroom/living20.jpg +laundromat/lavanderia.jpg +winecellar/bodega_vino_03_10_altavista.jpg +library/bnf.jpg +cloister/bt_cloister.jpg +lobby/Ashburton_Meeting_Room_003.jpg +mall/2007_02_galleria.jpg +office/office.jpg +videostore/videoclub_01_15_altavista.jpg +hospitalroom/roomhospital.jpg +nursery/lit_bois_bebe_01_1.jpg +videostore/videoteca_07_04_flickr.jpg +stairscase/escalier_chapelle_loretto2.jpg +classroom/Classroom1.jpg +fastfood_restaurant/800px_Nandos_dhanmondi.jpg +prisoncell/prison_cell_48_07_altavista.jpg +fastfood_restaurant/Taco_Bell_8.jpg +artstudio/modern_art_studio_50_11_altavista.jpg +gym/gimnasio_11_12_altavista.jpg +corridor/corridora7.jpg +church_inside/Inside_a_large_church_Vienna.jpg +auditorium/image_preview_1__149.jpg +mall/thumb_3aa796fc07fe0a44839609f6fffb7384_a1151d110007d93035896c753526fed3.jpg +gym/1852272142_7c45e7c342.jpg +livingroom/living23.jpg +pantry/pantry_09_02_altavista.jpg +classroom/classroom05.jpg +videostore/videoteca_06_24_flickr.jpg +laboratorywet/nathanfeinlab.jpg +mall/Galleria_1.jpg +cloister/Claustro1.jpg +lobby/sLobby05.jpg +auditorium/stevensstraight_72.jpg +bedroom/indoor_0578.jpg +laundromat/ins7.jpg +subway/underground_08_12_altavista.jpg +gameroom/LV_00_05_4690_34_l.jpg +videostore/videoclub_01_13_altavista.jpg +fastfood_restaurant/Taco_Bell_7.jpg +inside_bus/inside_bus_051.jpg +laboratorywet/laboratorio_quimica_13_01_altavista.jpg +warehouse/warehouse_0447.jpg +church_inside/597086.jpg +locker_room/locker_room_google_0116.jpg +hairsalon/Picture_013.jpg +prisoncell/prison4.jpg +cloister/Claustro_Monasterio_San_Juan_de_los_Reyes.jpg +inside_bus/inside_bus_043.jpg +studiomusic/82793611.oGmwNa9a.20070629studio01comp.jpg +cloister/claustro_silos.jpg +children_room/VA_05_02_8100_37_l.jpg +bakery/bakery_21_02_yahoo.jpg +library/34_AvH_014_library_stacks.jpg +buffet/food.jpg +library/450px_Bibliothek_im_Reformierten_Kollegium_Debrecen.jpg +clothingstore/b0011.jpg +lobby/hall_entree.jpg +toystore/toys_store_14_14_altavista.jpg +jewelleryshop/e_jewelry_store_13_.jpg +restaurant_kitchen/restaurant_kitchen_google_0035.jpg +toystore/jugueteria_02_15_flickr.jpg +grocerystore/MainFoodStoreProduce1.jpg +livingroom/easyst035.jpg +lobby/entrhallstair66.jpg +elevator/elevator_google_0003.jpg +subway/subway_0319.jpg +bakery/famous_bakery_04_03_altavista.jpg +movietheater/auditorium12_35.jpg +subway/subway_0027.jpg +artstudio/art_painting_studio_22_03_altavista.jpg +kitchen/dsc01464.jpg +bakery/new_bakery_30_07_altavista.jpg +fastfood_restaurant/DSC00471.jpg +bookstore/bookstore_46_15_altavista.jpg +bathroom/IMG_1703.jpg +pantry/300_78375.jpg +poolinside/OR_04_03_0005_23_l.jpg +hairsalon/bez2.jpg +bookstore/livMezanino2.jpg +children_room/VA_05_20_0020_15_l.jpg +kindergarden/play_area_and_windows_large2.jpg +operating_room/operating_room_15_19_altavista.jpg +bowling/bowling_0151.jpg +poolinside/piscina_cubierta_02_18_altavista.jpg +bathroom/008.jpg +livingroom/easyst020.jpg +garage/garajep0.jpg +operating_room/surgery_room_45_01_altavista.jpg +waitingroom/salleAttente.jpg +grocerystore/DSCN0258.jpg +locker_room/locker_room_google_0201.jpg +airport_inside/airport_inside_0022.jpg +cloister/Cloister_Nov03_D3598sAR800.jpg +computerroom/PICT0004.jpg +studiomusic/electronic_music1.jpg +airport_inside/airport_inside_0001.jpg +restaurant/restaurant_46_05_altavista.jpg +elevator/elevator_google_0076.jpg +corridor/corridora6.jpg +inside_bus/inside_bus_061.jpg +mall/galleria2.jpg +classroom/fourmies_ecomusee2.jpg +kitchen/kitchen46.jpg +cloister/claustro29dd.jpg +inside_subway/inside_subway_0134.jpg +concert_hall/NorwoodConcertHall_copy.jpg +jewelleryshop/Interior_3.jpg +livingroom/Living66.jpg +shoeshop/zapateria_09_05_altavista.jpg +cloister/claustropi7.jpg +laundromat/OR_03_05_1000_59_l.jpg +library/spellbinding_bookstore.jpg +restaurant_kitchen/restaurant_kitchen_google_0105.jpg +children_room/playroom2_30_.jpg +bowling/bowling_0017.jpg +fastfood_restaurant/800px_Lincoln_park_chicago_qdoba.jpg +videostore/video01.jpg +church_inside/church15_1.jpg +greenhouse/747202.jpg +warehouse/warehouse_0458.jpg +children_room/AT_04_05_6000_54_l.jpg +concert_hall/salle_de_concert.jpg +subway/subway_0071.jpg +elevator/elevator_google_0072.jpg +airport_inside/airport_inside_0267.jpg +buffet/food_4904.jpg +church_inside/pantry_120_09_flickr.jpg +inside_bus/inside_bus_008.jpg +garage/12_x_24_Garage_Interior_.jpg +closet/075381028998.jpg +gym/gimnasio_32_04_altavista.jpg +auditorium/amphitheatre_1__85.jpg +studiomusic/control_water_music.jpg +greenhouse/invernaderos_fotos_020.jpg +cloister/I007CloisterMoyneAbbey.jpg +warehouse/warehouse_0086.jpg +fastfood_restaurant/panther_grill_gif.jpg +hairsalon/c6fde69b_523f.jpg +stairscase/TA_99_3_0525_33_l.jpg +inside_subway/inside_subway_0358.jpg +shoeshop/shoeshop.jpg +artstudio/estudio_de_pintor_02_07_altavista.jpg +closet/Laminated_Clothing_Closet.jpg +concert_hall/concertFull.jpg +restaurant/restaurante_20_04_altavista.jpg +gym/gimnasio_57_15_flickr.jpg +clothingstore/rotterdam.jpg +closet/Maple_DH_Closet_Propped.jpg +museum/museum_120_08_flickr.jpg +toystore/toys_store_01_13_altavista.jpg +bookstore/Librairie_36_11_altavista.jpg +dentaloffice/dental_office_11_12_altavista.jpg +bookstore/bookstore_02_09_altavista.jpg +greenhouse/hydroponic_greenhouse_1.jpg +gym/gimnasio_23_17_altavista.jpg +greenhouse/site_estufa_03.jpg +bar/bar_0246.jpg +bathroom/indoor_0407.jpg +clothingstore/our_boutique011.jpg +airport_inside/airport_inside_0078.jpg +fastfood_restaurant/30835528_scaled_408x271.jpg +deli/deli_85_04_flickr.jpg +museum/museum_41_15_altavista.jpg +pantry/ShelfTrack_Pantry.jpg +gym/Gym_Equipment_Cardio.jpg +jewelleryshop/storefloor_52_.jpg +pantry/WH_Sidelines_Baskets_Pantry.jpg +casino/casino_0023.jpg +classroom/classroom04.jpg +toystore/Spielzeug_29_02_flickr.jpg +waitingroom/Waiting_Room_sm.jpg +meeting_room/conference6.jpg +meeting_room/c17.jpg +hospitalroom/SUDEEP_HOSPITAL.jpg +museum/museum_08_09_flickr.jpg +grocerystore/1ng10a.jpg +videostore/blockbuster_22_05_altavista.jpg +florist/floreria_02_13_flickr.jpg +buffet/528370197_65f7ccd505.jpg +bowling/bowling_0214.jpg +shoeshop/zapateria_24_16_flickr.jpg +classroom/236254301_fa02eae064.jpg +fastfood_restaurant/101_0301.jpg +livingroom/book_living_room.jpg +hairsalon/salondebelleza.jpg +airport_inside/airport_inside_0061.jpg +nursery/nursery_moon_theme800px.jpg +casino/casino_0013.jpg +warehouse/warehouse_0026.jpg +hospitalroom/smartroomdrhassan_hi.jpg +kitchen/kitchen1.jpg +stairscase/INT69.jpg +artstudio/ateliers_11_09_altavista.jpg +subway/subway_0217.jpg +bedroom/ph_02_04_4682_01_l.jpg +jewelleryshop/50_7_.jpg +auditorium/amphitheatre_2__86.jpg +library/mainLibrary.jpg +elevator/elevator_google_0035.jpg +museum/museum_42_12_altavista.jpg +office/mayors_office.jpg +museum/museo_86_11_flickr.jpg +stairscase/AT_04_02_3000_35_l.jpg +dentaloffice/dentista_22_16_flickr.jpg +inside_bus/inside_bus_035.jpg +kindergarden/IMG_1522_18203029_std.jpg +deli/deli_62_15_flickr.jpg +videostore/videoteka_02_12_altavista.jpg +deli/deli_98_18_flickr.jpg +subway/metropolitana_15_12_flickr.jpg +lobby/sLobby26.jpg +tv_studio/tv_studio11_21_07_113_.jpg +concert_hall/slleconcert.jpg +locker_room/locker_room_google_0200.jpg +warehouse/warehouse_0317.jpg +inside_bus/inside_bus_005.jpg +subway/subway_0312.jpg +hospitalroom/saifee_bed.jpg +jewelleryshop/5000000_8_.jpg +operating_room/surgery_room_08_13_altavista.jpg +elevator/elevator_google_0067.jpg +garage/Garage_03.jpg +movietheater/movietheater_google_0019.jpg +waitingroom/waiting_room_41_04_altavista.jpg +bathroom/b4.jpg +classroom/classroom_001.jpg +dining_room/roomscan24.jpg +trainstation/estacion_de_ferrocarriles_01_21_flickr.jpg +greenhouse/hydroponic_greenhouse.jpg +studiomusic/pic3.jpg +garage/Garage890.jpg +grocerystore/supermarche33_1.jpg +dining_room/dining001.jpg +operating_room/surgery_room_44_02_altavista.jpg +bakery/new_bakery_26_09_altavista.jpg +elevator/elevator_google_0009.jpg +movietheater/auditorium39_16.jpg +pantry/pantry_50_20_flickr.jpg +hospitalroom/lister_hospital_05.jpg +library/bibliothekd.jpg +bedroom/dsc04183.jpg +laboratorywet/laboratorio_quimica_18_01_altavista.jpg +locker_room/locker_room_google_0055.jpg +computerroom/fitxer75b_IMG_1581a.jpg +dining_room/dining38.jpg +museum/museum_26_20_altavista.jpg +prisoncell/prison_cell_26_02_altavista.jpg +elevator/elevator_google_0077.jpg +cloister/Abbaye_Royale_de_Fontevraud_IMG_5466.jpg +computerroom/cr3.jpg +kindergarden/prek1.jpg +computerroom/Computer_Room_5a.jpg +elevator/elevator_google_0014.jpg +museum/museo_32_09_flickr.jpg +operating_room/operating_room_21_09_altavista.jpg +mall/mall14.jpg +laboratorywet/feinlabcentrifuge.jpg +tv_studio/set_camera2_465x370_97_.jpg +bedroom/n190033.jpg +hairsalon/Copie_de_DSCF1579.jpg +hospitalroom/DSC_0001.jpg +mall/663265908_597972e155.jpg +lobby/egypt004.jpg +tv_studio/estudio_de_television_04_14_altavista.jpg +stairscase/N25.jpg +museum/museo_67_23_flickr.jpg +elevator/elevator_google_0097.jpg +prisoncell/jailcell334.jpg +bar/bar_0186.jpg +library/gallerie_1130426509812_81_80_90_133.jpg +greenhouse/greenhouse98.jpg +dentaloffice/dentista_09_20_altavista.jpg +pantry/1128_pantry.jpg +fastfood_restaurant/restaurant.jpg +operating_room/surgery_room_06_19_altavista.jpg +shoeshop/zapabulo02.jpg +hairsalon/4613843c_peluqueria2.jpg +lobby/vilnius_neringa_hotel_lobby.jpg +locker_room/locker_room_google_0007.jpg +subway/subway_0092.jpg +warehouse/warehouse_0376.jpg +florist/floristeria_belinda.jpg +laundromat/image052.jpg +locker_room/locker_room_google_0238.jpg +corridor/c18.jpg +poolinside/room175.jpg +dentaloffice/24304190.jpg +mall/386349.jpg +bowling/bowling_0158.jpg +locker_room/locker_room_google_0073.jpg +kindergarden/toddler1.jpg +pantry/pantry_123_06_flickr.jpg +prisoncell/1793365943_cc1f2e4d69.jpg +deli/new_deli_41_12_altavista.jpg +trainstation/train_station_01_05_altavista.jpg +bakery/boulangerie_28_03_yahoo.jpg +airport_inside/airport_inside_0174.jpg +trainstation/gare_127_13_flickr.jpg +nursery/Room1.jpg +poolinside/pool_inside_15_13_altavista.jpg +trainstation/gare_06_01_flickr.jpg +airport_inside/airport_inside_0038.jpg +tv_studio/plato_de_television_02_03_altavista.jpg +shoeshop/zapateria_12_06_flickr.jpg +winecellar/bodega_vino_04_05_altavista.jpg +garage/garage876.jpg +grocerystore/dsc011758jj.jpg +mall/2120.jpg +inside_subway/inside_subway_0297.jpg +bar/bar_0190.jpg +cloister/14claustro.jpg +inside_subway/inside_subway_0105.jpg +operating_room/surgery_room_41_01_altavista.jpg +museum/museo_127_07_flickr.jpg +children_room/507_playroom_8_.jpg +gym/Exercise_Gym.jpg +children_room/TA_99_3_0523_01_l.jpg +livingroom/indoor_0325.jpg +videostore/videoclub_14_07_altavista.jpg +elevator/elevator_google_0047.jpg +fastfood_restaurant/451033421_2252a21567.jpg +meeting_room/conf05.jpg +church_inside/hottinger_kirche_neu_innen.jpg +studiomusic/4683183.jpg +hairsalon/11555455012.jpg +tv_studio/tv_studio_03_18_altavista.jpg +artstudio/artist_studio_30_14_altavista.jpg +concert_hall/dpac_int.jpg +cloister/cloistetr.jpg +winecellar/cave_champagne_01_19_altavista.jpg +garage/garage99.jpg +bathroom/d47.jpg +computerroom/MCentre06.jpg +library/image_bibliotheque.jpg +stairscase/room191.jpg +operating_room/surgery_room_14_05_altavista.jpg +waitingroom/BATH06.jpg +gym/gym_floor_mirror_view.jpg +gameroom/salle_de_jeux_10_17_altavista.jpg +greenhouse/greenhouseikjj2.jpg +bar/bar_0063.jpg +livingroom/living14.jpg +museum/museum_101_16_flickr.jpg +restaurant/Salle_de_Restaurant_2.jpg +restaurant_kitchen/restaurant_kitchen_google_0087.jpg +winecellar/wine_cellar_12_14_altavista.jpg +gym/gimnasio_20_13_altavista.jpg +studiomusic/djban_estudio_01.jpg +hairsalon/CarrollCountyHairSalonStaff.jpg +inside_subway/inside_subway_0391.jpg +shoeshop/193499659_cc6d11184c.jpg +classroom/ClassroomElem.jpg +winecellar/wine_storage_21_19_altavista.jpg +meeting_room/n457005.jpg +laboratorywet/Ion_Chromatography.jpg +videostore/videoteca_04_11_flickr.jpg +jewelleryshop/05.jpg +florist/r0.jpg +nursery/600_nontoxic_nursery_crib.jpg +garage/mso1012_BackWall_bef_w609.jpg +bar/bar_0101.jpg +bathroom/ta_99_2_0319_02_l.jpg +laundromat/lavanderia_41_07_flickr.jpg +museum/museo_54_09_flickr.jpg +jewelleryshop/jewelrystore002_20_.jpg +trainstation/gare_152_14_flickr.jpg +bathroom/bath288.jpg +closet/closet_rack.jpg +children_room/playroom_decorating_ideas_239_51_.jpg +grocerystore/supermarket_fluores.jpg +stairscase/N190086.jpg +museum/museum_46_08_altavista.jpg +greenhouse/url.jpg +studiomusic/int773.jpg +laundromat/image_preview.jpg +warehouse/warehouse_0120.jpg +closet/closet33.jpg +inside_bus/inside_bus_090.jpg +videostore/videoclub_10_22_flickr.jpg +closet/MasterSuiteLaminateWIBR.jpg +clothingstore/Bookstore2.jpg +inside_bus/inside_bus_101.jpg +meeting_room/int283.jpg +office/or_02_05_0479_11_l.jpg +poolinside/room239.jpg +stairscase/N05m.jpg +dentaloffice/dentista_128_16_flickr.jpg +elevator/elevator_google_0085.jpg +casino/casino_0256.jpg +children_room/pippins_main_play_room_28_.jpg +office/neutbbl.t.jpg +greenhouse/greenhouse2yy1.jpg +subway/subway_0342.jpg +stairscase/N457087.jpg +warehouse/warehouse_0038.jpg +concert_hall/concert_theater_photo01.jpg +gameroom/sala_de_juegos_16_19_altavista.jpg +cloister/01Claustro.jpg +bedroom/bedroom18.jpg +artstudio/art_painting_studio_43_06_altavista.jpg +videostore/videoclub_34_03_altavista.jpg +deli/deli_11_04_yahoo.jpg +bowling/bowling_0134.jpg +restaurant_kitchen/restaurant_kitchen_google_0003.jpg +bakery/boulangerie_14_21_yahoo.jpg +bedroom/indoor_0323.jpg +concert_hall/grieghalle05.jpg +dining_room/dining008.jpg +gameroom/LV_04_01_0004_22_l.jpg +gameroom/gameroom2.jpg +tv_studio/tv_studio_19_02_altavista.jpg +inside_subway/inside_subway_0208.jpg +subway/subway_0315.jpg +winecellar/wine_cellar_36_17_altavista.jpg +elevator/elevator_google_0078.jpg +winecellar/wine_cellar_47_08_altavista.jpg +children_room/playroom16_58_.jpg +office/n457024.jpg +cloister/VeniceSantApolloniaCloister.jpg +laundromat/lavanderia_39_02_flickr.jpg +restaurant_kitchen/restaurant_kitchen_google_0100.jpg +studiomusic/clients_forge2.jpg +auditorium/auditorium_17_1__42.jpg +computerroom/IMG_0595.jpg +videostore/onyeze.jpg +children_room/skd_photo_playroom_69_.jpg +inside_bus/inside_bus_067.jpg +lobby/lobby1332.jpg +gameroom/gameroom789.jpg +bathroom/IMG_1701.jpg +bedroom/at_01_5a_1420_19_l.jpg +laboratorywet/wet_lab_09_01_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0059.jpg +studiomusic/p_14_Music_studio.jpg +toystore/245006402_b27063dfcd.jpg +clothingstore/mg004.jpg +dentaloffice/dentaloffice04.jpg +dining_room/d15b.jpg +jewelleryshop/532g.jpg +toystore/PICT0009.jpg +mall/mall24.jpg +kitchen/room14.jpg +videostore/picvideostore.jpg +children_room/playroom4500_64_.jpg +classroom/Pinas.jpg +airport_inside/airport_inside_0120.jpg +winecellar/cave_champagne_04_15_altavista.jpg +gameroom/sala_de_juegos_17_13_altavista.jpg +auditorium/anderson_1__88.jpg +lobby/lobby_reception_1.jpg +office/tuscan_home_office500A.jpg +bowling/bowling_0125.jpg +trainstation/gare_41_15_flickr.jpg +artstudio/painters_studio_29_02_altavista.jpg +bathroom/bath287.jpg +closet/closet_design_lg_gif.jpg +prisoncell/JAIL4.jpg +auditorium/img_1211445319655_1__150.jpg +cloister/en_el_claustro.jpg +garage/truckgarage.jpg +livingroom/l13.jpg +lobby/SD_00_01_51255_17_l.jpg +restaurant/restaurant_51_20_altavista.jpg +pantry/closetstoragekitchen.jpg +operating_room/surgery_room_03_06_altavista.jpg +lobby/lobby03.jpg +restaurant/restaurant_20_06_altavista.jpg +pantry/despensa_32_20_flickr.jpg +grocerystore/shop15.jpg +poolinside/17429_1_Pool7Inside.jpg +prisoncell/610x.jpg +nursery/babyroom_1.jpg +subway/subway_0356.jpg +videostore/610x.jpg +gameroom/int511.jpg +warehouse/warehouse_0378.jpg +gameroom/salle_de_jeux_08_14_altavista.jpg +lobby/Foyer_Lobby.jpg +church_inside/church234567.jpg +bar/bar_0156.jpg +grocerystore/supermercato_fs.jpg +shoeshop/zapateria_01_16_flickr.jpg +auditorium/auditorium098_105.jpg +grocerystore/IMG_4862BeerinNewHampshire.jpg +mall/Buenos_Aires_shopping_center_2_.jpg +office/despacho2_400.jpg +computerroom/computadores.jpg +greenhouse/greenhouse2123.jpg +museum/museum_137_18_flickr.jpg +bar/bar_0166.jpg +church_inside/1347447925_425d67a816.jpg +library/library01.jpg +garage/ihs_house_garage_inside.jpg +warehouse/warehouse_0036.jpg +dentaloffice/dental_office_49_11_altavista.jpg +jewelleryshop/1202199587interior.jpg +children_room/VA_03_00_0003_44_l.jpg +restaurant/restaurant_05_20_altavista.jpg +auditorium/g_5eauditorium_1__147.jpg +dining_room/room272.jpg +garage/Garage_rollup_open_1_262135554_std.jpg +dining_room/dining011.jpg +nursery/amys_baby_room_lg.jpg +artstudio/int12.jpg +subway/subway_0474.jpg +dining_room/n457043.jpg +buffet/buffet648.jpg +locker_room/locker_room_google_0166.jpg +toystore/Spielzeug_84_23_flickr.jpg +closet/MasterSuiteWI.jpg +buffet/food_table03.jpg +gameroom/gameroom_web.jpg +corridor/p1010068_c.jpg +auditorium/facility_rentals_auditorium_1__146.jpg +greenhouse/DSCF0009.jpg +shoeshop/f339.jpg +airport_inside/airport_inside_0323.jpg +shoeshop/shoes_shop_01_20_altavista.jpg +studiomusic/clients_forge1.jpg +winecellar/wine_cellar_09_18_altavista.jpg +computerroom/salle_info.jpg +prisoncell/todd_232087_14.jpg +restaurant/restaurant_20_18_altavista.jpg +auditorium/auditorium712_38.jpg +kindergarden/DSC000572.jpg +poolinside/piscina_cubierta_03_12_altavista.jpg +videostore/DVD3268.jpg +gameroom/sala_de_juegos_13_14_altavista.jpg +garage/mso1004_StuffOnWalls_aft1_w609.jpg +laboratorywet/laboratorio_quimica_01_18_altavista.jpg +winecellar/wine_cellar_47_19_altavista.jpg +children_room/LV_98_3_7013_14_l.jpg +bakery/boulangerie_38_18_altavista.jpg +greenhouse/BA_Greenhouse.jpg +prisoncell/JailCell1950.jpg +grocerystore/caraman_supermarche_90d5b.jpg +artstudio/art_painting_studio_43_20_altavista.jpg +toystore/toys_store_36_07_altavista.jpg +videostore/videoclub_30_06_altavista.jpg +fastfood_restaurant/Blimpie21.jpg +hospitalroom/hospital_room_07_16_altavista.jpg +kindergarden/Classroom566.jpg +buffet/fffffda05b0b.jpg +inside_subway/inside_subway_0183.jpg +nursery/kimberly_grant.jpg +kitchen/room40.jpg +bakery/panetteria_10_10_altavista.jpg +inside_bus/inside_bus_070.jpg +children_room/playroom_000_66_.jpg +hairsalon/fdc_2701_1.jpg +videostore/videoteca_05_12_flickr.jpg +kindergarden/Classroom_109.jpg +laundromat/laundry_room_area.jpg +bowling/bowling_0036.jpg +buffet/buffet87.jpg +church_inside/144952333.jpg +classroom/class_room_2.jpg +bakery/panetteria_01_01_altavista.jpg +gym/VAVORITE_GYM_SHOT_DIGITAL.jpg +concert_hall/concert2.jpg +kindergarden/classA01.jpg +livingroom/familyroom97.jpg +fastfood_restaurant/149212593_1abe45be1e.jpg +bathroom/indoor_0197.jpg +lobby/egypt005.jpg +bowling/bowling_0029.jpg +lobby/lobby31.jpg +pantry/White_WI_Pantry_woDining_Rm.jpg +garage/Garage7654.jpg +warehouse/warehouse_0216.jpg +concert_hall/19052005180320_6.jpg +winecellar/bodega_38_21_yahoo.jpg +museum/museo_43_04_flickr.jpg +kindergarden/Preschool_Place_Market.jpg +garage/GarageInside.jpg +lobby/16.jpg +inside_bus/inside_bus_071.jpg +meeting_room/conf08.jpg +children_room/girlsplayroom_18_.jpg +casino/casino_0041.jpg +jewelleryshop/170208.jpg +restaurant/bistro_43_06_altavista.jpg +videostore/video_store_05_01_altavista.jpg +corridor/p1010076_c.jpg +greenhouse/_7CCDF0C3_D08A_420C_8275_8544C0A84D0C_19112003_002.jpg +operating_room/surgery_room_06_04_altavista.jpg +poolinside/pool_inside_16_02_altavista.jpg +trainstation/train_station_11_18_altavista.jpg +office/water_office.jpg +stairscase/stairs07.jpg +bakery/bakery_23_13_yahoo.jpg +tv_studio/estudio_rbs_tv_bage_44_.jpg +toystore/toys_store_14_15_altavista.jpg +trainstation/gare_97_15_flickr.jpg +buffet/buffet_food.jpg +grocerystore/veg_isle1.jpg +laundromat/PO_06_03_3000_81_l.jpg +elevator/elevator_google_0074.jpg +mall/ins47.jpg +hairsalon/ricardo_maggiore_salon.jpg +subway/subway_0259.jpg +trainstation/gare_67_13_flickr.jpg +grocerystore/integral_color4_detail.jpg +dining_room/n15m.jpg +office/interior004.jpg +hairsalon/salon_coiffure_ca.jpg +meeting_room/conf20.jpg +deli/deli_08_18_yahoo.jpg +bedroom/int139.jpg +restaurant/restaurante_40_11_altavista.jpg +bowling/bowling_0035.jpg +operating_room/operating_room_04_19_altavista.jpg +pantry/despensa_99_09_flickr.jpg +casino/casino_0077.jpg +tv_studio/estudio028_86_.jpg +cloister/Graus_Claustro.jpg +videostore/videoclub_03_23_flickr.jpg +bedroom/IMG_9816.jpg +jewelleryshop/shopinterior.jpg +hospitalroom/UGHHPD010.jpg +dining_room/dining2.jpg +poolinside/schwimmbad003.jpg +bar/bar_0435.jpg +inside_subway/inside_subway_0171.jpg +subway/subway_0460.jpg +videostore/videoclub_04_19_flickr.jpg +closet/8c.jpg +prisoncell/jail_cell22.jpg +warehouse/warehouse_0333.jpg +bar/bar_0376.jpg +concert_hall/GTPR_DiGiCo_VAC_04_LR.jpg +casino/casino_0236.jpg +warehouse/warehouse_0154.jpg +dining_room/d3.jpg +gameroom/gameroom_pocket.jpg +restaurant_kitchen/restaurant_kitchen_google_0010.jpg +waitingroom/Bistro_3_BMP.jpg +dentaloffice/dentaire_11_13_flickr.jpg +poolinside/0310_piscines_9.jpg +classroom/sdeclasse2.jpg +fastfood_restaurant/chip2.jpg +florist/floreria_08_08_flickr.jpg +casino/casino_0232.jpg +toystore/jugueteria_04_03_flickr.jpg +casino/casino_0018.jpg +bookstore/Librairie_48_13_altavista.jpg +greenhouse/invernadero_flores2.jpg +hairsalon/1871389960_44f51e02e4.jpg +kindergarden/pre2.jpg +mall/3163.jpg +artstudio/artist_studio_24_05_altavista.jpg +office/int85.jpg +bathroom/indoor_0187.jpg +waitingroom/WebsiteWaitingRoom.jpg +corridor/pasillo33_c.jpg +locker_room/locker_room_google_0031.jpg +kitchen/kitchen037.jpg +studiomusic/int806.jpg +bowling/bowling_0183.jpg +trainstation/gare_03_09_altavista.jpg +winecellar/wine_cellar_17_19_altavista.jpg +office/o3.jpg +locker_room/locker_room_google_0227.jpg +corridor/p1010069_c.jpg +deli/deli_47_17_flickr.jpg +poolinside/69214_1.jpg +mall/mbk_center.jpg +bookstore/bookstore_41_04_altavista.jpg +computerroom/Salle_Informatique_Bellavista_060831_2_1.jpg +trainstation/gare_87_18_flickr.jpg +gym/gimnasio_02_04_altavista.jpg +artstudio/art_painting_studio_01_10_altavista.jpg +casino/casino_0265.jpg +dentaloffice/dental_office_46_03_altavista.jpg +grocerystore/shop14.jpg +corridor/p1010072_c.jpg +bakery/144215_Delicious_bakery_0.jpg +jewelleryshop/jewelry_store_case_1_9_21_.jpg +locker_room/locker_room_google_0242.jpg +studiomusic/fqtr_estudio.jpg +bowling/bowling_0087.jpg +restaurant_kitchen/restaurant_kitchen_google_0106.jpg +clothingstore/Lacys_4.jpg +auditorium/d38_img_1758_1__145.jpg +fastfood_restaurant/dining_room.jpg +gym/MSAC_Gym_20061515.jpg +nursery/nursery1.jpg +winecellar/Restaurant_Wine_Cellar_larg.jpg +meeting_room/conference_room4.jpg +casino/casino_0157.jpg +library/store.jpg +library/association_bibliotheque.jpg +gym/gimnasio_40_24_flickr.jpg +casino/casino_0455.jpg +hospitalroom/HospitalRoom.jpg +nursery/baba7.jpg +children_room/AT_99_1_7839_22_l.jpg +toystore/speelgoed_54_22_flickr.jpg +lobby/erasme_acceuil.jpg +church_inside/3810071_l.jpg +elevator/elevator_google_0045.jpg +meeting_room/conference_room_600.jpg +restaurant_kitchen/restaurant_kitchen_google_0094.jpg +bakery/new_bakery_33_09_altavista.jpg +florist/floreria_05_07_flickr.jpg +hairsalon/onglet_salon_d_application_paragraphe_coiffure.jpg +movietheater/movietheater_google_0035.jpg +museum/museum_127_04_flickr.jpg +office/office17.jpg +bowling/bowling_0111.jpg +gym/HO_00_02_5304_28A_l.jpg +closet/closet12345.jpg +children_room/hiemstra_011_19_.jpg +pantry/pantry_145_11_flickr.jpg +movietheater/movietheater_google_0040.jpg +auditorium/auditorium_back_30.jpg +garage/GarageInside1.jpg +library/763634302_e25f44402d.jpg +lobby/lobby6.jpg +nursery/enfant2.jpg +subway/subway_0478.jpg +bar/bar_0056.jpg +church_inside/kirche_rueckansicht.jpg +gym/refurbished_gym_equipment.jpg +restaurant_kitchen/restaurant_kitchen_google_0092.jpg +closet/LargeWalkin.jpg +bowling/bowling_0145.jpg +closet/JLH_deluxemaplea.jpg +bathroom/interior015.jpg +inside_bus/inside_bus_026.jpg +deli/deli_34_20_flickr.jpg +garage/3_CAR_GARAGE_1_1_25782352_std.jpg +inside_subway/inside_subway_0269.jpg +studiomusic/364906b0.jpg +shoeshop/zapato_1.jpg +florist/fleuriste_69.jpg +nursery/2008_08_05_abc_nursery.jpg +bakery/boulangerie_04_12_altavista.jpg +florist/floreria_03_18_flickr.jpg +library/207157437_14c21369e9.jpg +classroom/classroom02.jpg +bowling/bowling_0078.jpg +stairscase/AT_04_03_2000_52_l.jpg +jewelleryshop/412531_2.jpg +meeting_room/conf03.jpg +artstudio/art_painting_studio_24_02_altavista.jpg +artstudio/art_painting_studio_15_16_altavista.jpg +jewelleryshop/inside_18_.jpg +kitchen/int35.jpg +dining_room/lv_02_03_9714_10_l.jpg +garage/CustomGarage006_1.jpg +toystore/165.jpg +bookstore/bookstore72.jpg +warehouse/warehouse_0235.jpg +inside_subway/inside_subway_0248.jpg +jewelleryshop/joyeria_156_16_flickr.jpg +bathroom/indoor_0332.jpg +garage/InsideDoor.jpg +hospitalroom/20080116aslocscreen3_500.jpg +gym/gimnasio_21_14_altavista.jpg +library/bibliotheaaak.jpg +church_inside/243069.jpg +hairsalon/hair_salon.jpg +toystore/109_0940.jpg +dining_room/indoor_0314.jpg +restaurant/restaurant_12_01_altavista.jpg +shoeshop/013D2RIO001_1.jpg +lobby/lobby56.jpg +greenhouse/greenhouse_981.jpg +nursery/babys_room.jpg +deli/deli_68_06_flickr.jpg +waitingroom/27_blood_donation_waiting_full.jpg +warehouse/warehouse_0075.jpg +buffet/lg_buffet_table.jpg +children_room/indoor_playroom_27_.jpg +museum/museo_124_20_flickr.jpg +auditorium/c54884a_19601d8_b_1__142.jpg +gameroom/OR_99_3_4797_22_l.jpg +stairscase/N190079.jpg +corridor/couloir_ch1_5e.jpg +locker_room/locker_room_google_0132.jpg +bathroom/indoor_0364.jpg +florist/florist_26_04_flickr.jpg +laboratorywet/laboratorio_quimica_07_11_altavista.jpg +hospitalroom/hospital_room_lighting.jpg +waitingroom/waiting_room03.jpg +casino/casino_0452.jpg +children_room/100_1678_4_.jpg +hairsalon/salon1_gr.jpg +warehouse/warehouse_0413.jpg +toystore/299724113_39b3a2b7f8.jpg +bakery/boulangerie_50_12_yahoo.jpg +auditorium/ptamphi_1__165.jpg +hairsalon/salon2.jpg +livingroom/indoor_0311.jpg +cloister/Picture159.jpg +auditorium/auditorium_from_stage_1__44.jpg +fastfood_restaurant/DSCF0039.jpg +elevator/elevator_google_0091.jpg +mall/31990541_ShanghaiDepartmentStoreNanjingRoad002.jpg +movietheater/sala_de_cine_13_15_altavista.jpg +winecellar/wine_cellar_air_conditioning.jpg +meeting_room/conference8.jpg +hospitalroom/room232.jpg +restaurant_kitchen/restaurant_kitchen_google_0086.jpg +bowling/bowling_0176.jpg +tv_studio/tv_studio_40_12_altavista.jpg +gym/118094196_dd8cc14df0.jpg +stairscase/treppe_birke_edelstahl_01.jpg +computerroom/BGEOG_salle_informatique.jpg +stairscase/Treppen_03.jpg +inside_bus/inside_bus_054.jpg +laboratorywet/01exbiochem.jpg +deli/deli_49_18_altavista.jpg +hospitalroom/ToFiladelfia_Hospital_Saddlery.jpg +prisoncell/prison_cell_41_02_altavista.jpg +videostore/videoclub_04_02_yahoo.jpg +closet/closet35.jpg +inside_bus/inside_bus_064.jpg +mall/y1pSu087_GTs3Qjf_zXRR_cQ05_sTqPwFpKHEiAthZJft3pnI3nF082_H06_1yvbjB5hJ0EhbGFFXE.jpg +studiomusic/studio_1.jpg +videostore/video_store_02_09_altavista.jpg +florist/floristeria3.jpg +greenhouse/bob_black_greenhouse.jpg +winecellar/wine_cellar_43_11_altavista.jpg +museum/museo_18_12_altavista.jpg +locker_room/locker_room_google_0052.jpg +operating_room/operating_room_23_20_altavista.jpg +operating_room/surgery_room_06_08_altavista.jpg +museum/museum_44_10_altavista.jpg +airport_inside/airport_inside_0281.jpg +garage/garage_11.jpg +kindergarden/actvitycenterwacr.jpg +livingroom/int4.jpg +greenhouse/invernadero_2.jpg +clothingstore/japan_2006_1153069200_harajuku2.jpg +inside_bus/inside_bus_018.jpg +videostore/blockbuster_11_05_flickr.jpg +subway/subway_0435.jpg +trainstation/estacion_de_ferrocarriles_28_14_altavista.jpg +hospitalroom/IMG_1901.jpg +children_room/fairfield_real_estate_85dogwood_playroom_750_17_.jpg +dentaloffice/dentista_35_11_flickr.jpg +bakery/panaderia_36_07_yahoo.jpg +laboratorywet/wet_lab_12_13_altavista.jpg +laundromat/685px_Laundromat_ontario.jpg +stairscase/int538.jpg +gym/gimnasio_21_11_altavista.jpg +meeting_room/n457000.jpg +trainstation/train_station_01_10_altavista.jpg +library/204942053_a5d52d7746.jpg +office/office1.jpg +closet/closet2_1.jpg +dining_room/int473.jpg +classroom/salle_classe.jpg +concert_hall/Goldenesaal.jpg +restaurant_kitchen/restaurant_kitchen_google_0027.jpg +corridor/c3.jpg +operating_room/surgery_room_19_02_altavista.jpg +hospitalroom/SuperStock_1444R-256189.jpg +jewelleryshop/mobicolor_8416.jpg +laboratorywet/wet_lab_13_04_altavista.jpg +bathroom/indoor_0486.jpg +office/despacho.jpg +bakery/boulangerie_12_11_yahoo.jpg +children_room/img_1896_23_.jpg +bedroom/indoor_0430.jpg +bar/bar_0313.jpg +corridor/c9.jpg +kindergarden/IMG_2325_t600.jpg +clothingstore/url.jpg +deli/deli_23_05_yahoo.jpg +garage/hdts1013_Garage_aft_photo_w609.jpg +dining_room/d8.jpg +poolinside/inside_romney.jpg +fastfood_restaurant/quick.jpg +trainstation/gare_99_24_flickr.jpg +classroom/auingl.jpg +bathroom/bothroom99.jpg +computerroom/dekdi3gv0.jpg +mall/2904502_Podium_shopping_mall_Mandaluyong.jpg +videostore/videoteca_07_05_flickr.jpg +prisoncell/prison_cell_08_19_altavista.jpg +casino/casino_0027.jpg +fastfood_restaurant/Blimpie_Columbia_002.jpg +hospitalroom/hospital_room_08_07_altavista.jpg +buffet/15_The_Buffet_small.jpg +tv_studio/estudio_de_television_01_04_altavista.jpg +classroom/244class.jpg +pantry/bob_connie_kitchen_pantry_after_02.jpg +greenhouse/segu_est.jpg +mall/mall22.jpg +meeting_room/c4.jpg +stairscase/L_Opera_Escalier_pr.jpg +studiomusic/lime_grove_music_studio_bob_foley.jpg +children_room/dsc_0090_16_.jpg +garage/ddt_3_03_garage_interior.jpg +stairscase/3Miguel_Angel_Vestibule_of_the_Laurentian_Library.jpg +grocerystore/P3260270.jpg +nursery/nursery___crib.jpg +operating_room/surgery_room_37_18_altavista.jpg +pantry/12.jpg +subway/subway_0418.jpg +corridor/p1010810_c.jpg +inside_subway/metropolitana_55_12_flickr.jpg +clothingstore/freeClothing.jpg +florist/p1_1.jpg +laundromat/Laundromatyu.jpg +gameroom/CA_97_5_294_22_l.jpg +jewelleryshop/joyeros.jpg +nursery/f3eda62a_8161_459e_9154_08c87eca8f96_player.jpg +tv_studio/estudio_fotografico_84_.jpg +auditorium/9_auditorium_20balcony_20contemporary_20_the_20carlu__20.jpg +casino/casino_0011.jpg +dining_room/d16.jpg +deli/deli_152_23_flickr.jpg +lobby/lobby_122222.jpg +subway/subway_0285.jpg +toystore/speelgoed_78_15_flickr.jpg +auditorium/auditorium_reserved_seating_003_sized_1__45.jpg +laundromat/lavanderia01.jpg +kitchen/kitchen089.jpg +restaurant/restaurant_09_10_altavista.jpg +auditorium/658_1_457_1__14.jpg +locker_room/locker_room_google_0135.jpg +prisoncell/hi_res.jpg +gameroom/AA_A9_GAMEROOM.jpg +artstudio/artist_work_place_07_07_altavista.jpg +garage/Garage_Inside_44.jpg +locker_room/locker_room_google_0186.jpg +grocerystore/Grocery02.jpg +casino/casino_0224.jpg +library/library_bookshelves_large.jpg +mall/p282857_Dsseldorf_Shopping_Mall.jpg +casino/casino_0436.jpg +museum/museo_72_07_flickr.jpg +library/68522859_a36fa01d31.jpg +casino/casino_0434.jpg +hairsalon/radius_main_17Mar2008101648939000.jpg +buffet/109484501_3d010b4367.jpg +library/scaihs_bookstore2.jpg +bedroom/indoor_0588.jpg +bowling/bowling_0003.jpg +tv_studio/xleague_tv_studio_with_steven_tu_76_.jpg +airport_inside/airport_inside_0295.jpg +deli/deli_103_09_flickr.jpg +corridor/c21.jpg +laundromat/lavanderia_72_12_flickr.jpg +dentaloffice/dentist1.jpg +movietheater/102_1__2.jpg +airport_inside/airport_inside_0523.jpg +dentaloffice/dentaloffice06.jpg +computerroom/comproom348.jpg +stairscase/int18.jpg +computerroom/hank_comproom_lg.jpg +dentaloffice/dentista_109_09_flickr.jpg +meeting_room/n457001.jpg +videostore/video_store470.jpg +restaurant_kitchen/restaurant_kitchen_google_0023.jpg +artstudio/artist_studio_33_16_altavista.jpg +warehouse/warehouse_0191.jpg +lobby/Hall1Planta.jpg +movietheater/salle_cinema.jpg +trainstation/gare_102_04_flickr.jpg +mall/center_point.jpg +mall/791.jpg +computerroom/computerroom08.jpg +elevator/elevator_google_0095.jpg +corridor/pasillo2_c.jpg +computerroom/open_computer_room.jpg +kindergarden/DSC05012.jpg +buffet/AsianDeliCounterWeb.jpg +casino/casino_0046.jpg +operating_room/operating_room_24_20_altavista.jpg +stairscase/room192.jpg +videostore/videoclub_03_19_flickr.jpg +bakery/bakery_07_10_yahoo.jpg +dining_room/dining16.jpg +restaurant_kitchen/restaurant_kitchen_google_0017.jpg +florist/florist_29_08_flickr.jpg +subway/subway_0043.jpg +auditorium/auditorium44_113.jpg +bowling/bowling_0175.jpg +airport_inside/airport_inside_0071.jpg +fastfood_restaurant/26338834.jpg +tv_studio/victor_tv_studio_121_.jpg +gameroom/sala_de_juegos_17_01_altavista.jpg +lobby/lobby22345.jpg +movietheater/cinema_altkirch_3_46.jpg +locker_room/locker_room_google_0054.jpg +computerroom/scuola06_2.jpg +lobby/pinelakeLOBBY01large.jpg +waitingroom/development_lab_wait_room_chairs.jpg +bakery/best_bakery_10_17_altavista.jpg +artstudio/artist_studio_49_10_altavista.jpg +fastfood_restaurant/2274247982_b8a09af8c5.jpg +waitingroom/hg_waiting_room_v.jpg +bathroom/room270.jpg +closet/url5678.jpg +jewelleryshop/sanfrancisco_poodle_ga_45_.jpg +livingroom/or_02_04_0211_14_l.jpg +hairsalon/peluqueria1.jpg +bedroom/indoor_0085.jpg +subway/subway_0366.jpg +florist/floreria_05_13_flickr.jpg +closet/elfaDecorBrchWhtDreamCloset_l.jpg +trainstation/gare_133_22_flickr.jpg +dining_room/lv_02_04_10917_23a_l.jpg +cloister/florence_Santa_Maria_Novella_Cloister.jpg +warehouse/warehouse_0081.jpg +laboratorywet/wet_lab_30_06_altavista.jpg +dentaloffice/dentista_128_15_flickr.jpg +laboratorywet/wet_lab_51_06_altavista.jpg +inside_subway/metropolitana_54_11_flickr.jpg +meeting_room/n457014.jpg +poolinside/pool_inside_16_03_altavista.jpg +bathroom/100_0281.jpg +inside_subway/inside_subway_0092.jpg +stairscase/lest_b.jpg +prisoncell/7_Jail_Cells.jpg +elevator/elevator_google_0005.jpg +bowling/bowling_0080.jpg +cloister/cloister2s.jpg +prisoncell/carcel_139_19_flickr.jpg +grocerystore/supermarche467.jpg +fastfood_restaurant/2008_02_mcdonalds_thumb.jpg +dining_room/dining006.jpg +inside_subway/inside_subway_0223.jpg +bakery/bakery_display_case.jpg +restaurant/restaurant_30_06_altavista.jpg +classroom/classroom03.jpg +prisoncell/Prison04.jpg +waitingroom/Waiting_Room_sized.jpg +florist/florist_64_05_flickr.jpg +hospitalroom/Elijah_040.jpg +shoeshop/1.jpg +subway/subway_0336.jpg +tv_studio/tv_studio_09_20_altavista.jpg +mall/GUM_Department_Store.jpg +dentaloffice/KovacsLSC1_web.jpg +movietheater/sala_de_cine_01_18_altavista.jpg +closet/1.jpg +trainstation/train_station_34_15_altavista.jpg +bathroom/dublin___apartamento___29_03_2007_006.jpg +fastfood_restaurant/ElRapido.jpg +gameroom/WedSocial_Graceland_GameRoom_2.jpg +jewelleryshop/joyeria_23_07_altavista.jpg +lobby/savoy_int_vestibule.jpg +movietheater/movietheater_google_0016.jpg +shoeshop/zapateria_03_20_flickr.jpg +office/interior003.jpg +kindergarden/toddler3.jpg +library/bookstore_more_books.jpg +corridor/indoor_0233.jpg +meeting_room/n457002.jpg +inside_bus/inside_bus_050.jpg +deli/deli_43_10_altavista.jpg +meeting_room/n457011.jpg +prisoncell/jail_cell_jpg.jpg +classroom/int57.jpg +locker_room/locker_room_google_0097.jpg +subway/subway_0204.jpg +nursery/nursery6.jpg +kindergarden/two1.jpg +fastfood_restaurant/taco_bell_waikiki_counter.jpg +trainstation/train_station_05_20_altavista.jpg +greenhouse/ART2901025.jpg +bedroom/indoor_0557.jpg +casino/casino_0039.jpg +laboratorywet/wet_lab_40_18_altavista.jpg +deli/new_deli_27_08_altavista.jpg +bowling/bowling_0182.jpg +prisoncell/JailCells.jpg +gameroom/sala_de_juegos_17_14_altavista.jpg +operating_room/operating_room_23_02_altavista.jpg +pantry/pantry_20_09_flickr.jpg +restaurant/restaurant_02_14_altavista.jpg +shoeshop/zapateria_09_15_flickr.jpg +trainstation/gare_148_21_flickr.jpg +casino/casino_0215.jpg +museum/museum_42_03_altavista.jpg +nursery/dsc00680_778004.jpg +operating_room/surgery_room_02_20_altavista.jpg +closet/MainPageCloset2.jpg +fastfood_restaurant/NYBCinside1.jpg +tv_studio/tv_studio_2b_71_.jpg +warehouse/warehouse_0394.jpg +inside_subway/inside_subway_0396.jpg +poolinside/piscine2.jpg +grocerystore/shop18.jpg +artstudio/art_painting_studio_31_09_altavista.jpg +warehouse/warehouse_0488.jpg +concert_hall/p09_symphony.jpg +movietheater/sala_de_cine_06_19_altavista.jpg +bathroom/room316.jpg +hospitalroom/hospitalroom1.jpg +library/fine_arts.jpg +library/image_preview.jpg +toystore/Peluqueria_043.jpg +videostore/02_02.jpg +inside_bus/inside_bus_056.jpg +gameroom/salle_de_jeux_05_19_altavista.jpg +hairsalon/salon_page.jpg +meeting_room/n457007.jpg +videostore/videoteca_06_20_flickr.jpg +elevator/elevator_google_0063.jpg +florist/fleuriste_008.jpg +videostore/videoclub_07_04_flickr.jpg +laundromat/404776986_34fc3c3d65.jpg +toystore/toys_store_43_15_altavista.jpg +bar/bar_0183.jpg +bookstore/bookstore_41_05_flickr.jpg +movietheater/2421694668_810471b4cc_1__7.jpg +gameroom/OR_97_3_5172_33_l.jpg +bedroom/b17.jpg +bowling/bowling_0189.jpg +operating_room/operating_room_11_07_altavista.jpg +buffet/133_3369_IMG.jpg +concert_hall/hall.jpg +dining_room/dining35.jpg +office/office_13.jpg +fastfood_restaurant/food_450.jpg +lobby/int52.jpg +toystore/UMW_Rummage_Before_Boutique_with_dolls_780356.jpg +dentaloffice/tworivers23.jpg +restaurant/restaurante_04_17_altavista.jpg +toystore/CIMG3313.jpg +restaurant/restaurant_05_04_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0048.jpg +movietheater/scr_bluetheatre2jan19.jpg +classroom/Classroom4.jpg +laboratorywet/wet_lab_18_02_altavista.jpg +buffet/31248686_d2f7aa4f63.jpg +toystore/jugueteria_14_15_flickr.jpg +bar/bar_0362.jpg +bookstore/Libreria_08_03_altavista.jpg +tv_studio/1189186334_extras_video_1_8_.jpg +closet/custom_closet.jpg +kitchen/kitchen055.jpg +studiomusic/recording_studio.jpg +airport_inside/airport_inside_0040.jpg +children_room/SD_00_03_4446_23_l.jpg +bedroom/indoor_0047.jpg +deli/deli_02_21_yahoo.jpg +pantry/23_org_Pantry.jpg +trainstation/estacion_de_tren_04_17_altavista.jpg +warehouse/warehouse_0111.jpg +bakery/boulangerie_19_13_yahoo.jpg +dining_room/Dining4.jpg +kindergarden/preschool_crafts.jpg +bowling/bowling_0109.jpg +casino/casino_0004.jpg +concert_hall/DInsidefromstage.jpg +bar/bar_0216.jpg +corridor/va_04_01_5000_32_l.jpg +garage/GarageInside2.jpg +artstudio/art_painting_studio_16_08_altavista.jpg +bowling/bowling_0031.jpg +buffet/208671604_b7839a0955.jpg +videostore/video_store.jpg +clothingstore/bg_149.jpg +church_inside/churchinterior02.jpg +mall/mall35.jpg +office/n457028.jpg +buffet/buffet678.jpg +bathroom/indoor_0279.jpg +restaurant/restaurant_51_11_altavista.jpg +nursery/shakery_square.jpg +buffet/P1011257_fs.jpg +church_inside/292054.jpg +tv_studio/set_camera_465x370_98_.jpg +videostore/videoteca_13_12_altavista.jpg +meeting_room/n457022.jpg +operating_room/operating_room_23_11_altavista.jpg +kitchen/kitchen054.jpg +bar/bar_0116.jpg +hairsalon/PELUQUERIA_EROTICA_1155_3.jpg +laundromat/img_2196was.jpg +studiomusic/Beach_Studio.jpg +prisoncell/pariscellR_450x348.jpg +hairsalon/salon_karin_pc080164.jpg +restaurant_kitchen/restaurant_kitchen_google_0033.jpg +classroom/Classroom11.jpg +gameroom/AT_04_05_5000_58_l.jpg +movietheater/sala_cine2.jpg +computerroom/mr_ordinateur.jpg +corridor/IMG_0065.jpg +casino/casino_0160.jpg +shoeshop/eaed369ae9edc95c6c795ba27783.jpg +elevator/elevator_google_0008.jpg +auditorium/auditorium09_104.jpg +museum/museum_01_01_altavista.jpg +tv_studio/0421_6_.jpg +corridor/p1010063_c.jpg +corridor/img_0005.jpg +mall/metcalf_south_09.jpg +bedroom/indoor_0341.jpg +dining_room/dining025.jpg +airport_inside/airport_inside_0169.jpg +corridor/pasillo.jpg +restaurant/Salle_restaurant_reduit.jpg +closet/gallery_1_large_schulte_closets.jpg +corridor/corridora1.jpg +nursery/chambre_enfant_apres.jpg +locker_room/locker_room_google_0015.jpg +children_room/LV_00_03_2844_08_l.jpg +jewelleryshop/30446026.jpg +pantry/pantry_775384.jpg +gameroom/AT_01_2B_5492_21A_l.jpg +bookstore/manga_at_the_bookstore.jpg +dining_room/fw_97_2_0861_5_l.jpg +pantry/2323232327Ffp3323Enu3D32473E33C83E83B83EWSNRCG3D32323B93B844425nu0mrj1_87161114_std.jpg +toystore/toys_store_48_16_altavista.jpg +tv_studio/tv_studio_01_18_altavista.jpg +museum/museum_149_08_flickr.jpg +studiomusic/JUK30_JP6_Hifi_RackLit.jpg +church_inside/malbis_church_altar_big.jpg +elevator/elevator_google_0075.jpg +florist/florist_40_12_altavista.jpg +mall/mall27.jpg +buffet/ImpBuffet_0268.jpg +deli/new_deli_21_15_altavista.jpg +hairsalon/Image016_17.jpg +bakery/bakery_23_06_yahoo.jpg +inside_bus/inside_bus_046.jpg +locker_room/locker_room_google_0220.jpg +operating_room/surgery_room_50_15_altavista.jpg +lobby/6059968_lobby.jpg +restaurant_kitchen/restaurant_kitchen_google_0015.jpg +clothingstore/MVC_014F.jpg +artstudio/homeoff006.jpg +studiomusic/djban_curso_dj_01.jpg +museum/museum_116_08_flickr.jpg +kitchen/kitchen15.jpg +casino/casino_0235.jpg +studiomusic/Estudio_Musica_1.jpg +deli/deli_110_20_flickr.jpg +dentaloffice/farr_11.jpg +shoeshop/zapateria_04_18_flickr.jpg +children_room/web_playroom_70_.jpg +children_room/2369460826_37998bb932_7_.jpg +dentaloffice/dentista_09_05_altavista.jpg +pantry/pantry_91_15_flickr.jpg +trainstation/room213.jpg +bar/bar_0126.jpg +greenhouse/letter_greenhouse.jpg +pantry/web_pantr1.jpg +dining_room/sa_97_4_757_21_l.jpg +nursery/nursery5.jpg +laboratorywet/wet_lab_45_04_altavista.jpg +lobby/Raffles_Marina_lobby.jpg +pantry/pantry_140_10_flickr.jpg +tv_studio/tv_studio_11_16_altavista.jpg +garage/onsite_insidebg.jpg +movietheater/prod_photo1_377_1146674705_1__32.jpg +buffet/450407910_0c165a37aa.jpg +church_inside/Kirche_Mitterdorf_0050_web.jpg +closet/closet_ts.jpg +garage/gar7.jpg +waitingroom/sala_de_espera_02_04_altavista.jpg +clothingstore/Lacys_6.jpg +buffet/june_29_FOOD_BUFFET_002.jpg +clothingstore/STORE_032.jpg +laboratorywet/wet_lab_10_14_altavista.jpg +toystore/magasin_jouet_39_19_altavista.jpg +casino/casino_0476.jpg +waitingroom/waitingroom.jpg +livingroom/l6.jpg +inside_subway/inside_subway_0277.jpg +fastfood_restaurant/HK_Pizza_Hut.jpg +inside_bus/inside_bus_013.jpg +florist/florist_15_14_altavista.jpg +gameroom/smallsp001.jpg +classroom/Wicoff_Classroom_1.jpg +corridor/p1010073_c.jpg +deli/deli_149_08_flickr.jpg +elevator/elevator_google_0026.jpg +inside_subway/inside_subway_0254.jpg +warehouse/warehouse_0176.jpg +laundromat/lavanderia_53_15_flickr.jpg +kitchen/kitchen033.jpg +auditorium/amphi_gb2_1_1__22.jpg +locker_room/locker_room_google_0199.jpg +dining_room/dining013.jpg +kindergarden/classroom12.jpg +laundromat/lavanderia_51_19_flickr.jpg +livingroom/living24.jpg +bowling/bowling_0042.jpg +restaurant/restaurant_29_06_altavista.jpg +gameroom/gameroom_c1.jpg +gameroom/salle_de_jeux_03_12_altavista.jpg +waitingroom/waiting_room_large.jpg +bakery/bakery_01_17_yahoo.jpg +bathroom/interior017.jpg +gameroom/OR_99_9_0039_06_l.jpg +warehouse/warehouse_0006.jpg +airport_inside/airport_inside_0204.jpg +artstudio/art_painting_studio_04_17_altavista.jpg +bedroom/roomscan2.jpg +prisoncell/Men_Jail2.jpg +laundromat/Ricks_laundromat_pictures_april_2007_133.jpg +pantry/pantry_87_18_flickr.jpg +warehouse/warehouse_0084.jpg +elevator/elevator_google_0054.jpg +grocerystore/super_market.jpg +laundromat/3170849.jpg +deli/deli_111_14_flickr.jpg +grocerystore/Supermercado1.jpg +subway/subway_0173.jpg +corridor/allee2yp.jpg +library/bibliotheque_0908.jpg +pantry/pantry_43_23_flickr.jpg +meeting_room/conference2.jpg +pantry/pantry_48_07_flickr.jpg +nursery/nursery_005.jpg +shoeshop/zapateria_27_21_flickr.jpg +kindergarden/DSC000512.jpg +classroom/classroom5.jpg +florist/11528159612.jpg +florist/ildo_encabo_150206_c_ok.jpg +bathroom/indoor_0124.jpg +elevator/elevator_google_0006.jpg +tv_studio/35_20_.jpg +museum/museo_157_13_flickr.jpg +nursery/completed_nursery_010.jpg +restaurant_kitchen/restaurant_kitchen_google_0037.jpg +bowling/bowling_0149.jpg +dining_room/room503.jpg +inside_subway/inside_subway_0169.jpg +museum/museo_137_20_flickr.jpg +toystore/59516206_45f74119d6.jpg +laboratorywet/wet_lab_12_17_altavista.jpg +bedroom/b29.jpg +bookstore/librairie_gif.jpg +museum/museo_158_20_flickr.jpg +concert_hall/ven_chall_big.jpg +florist/floreria_08_21_flickr.jpg +computerroom/ordinateurs.jpg +concert_hall/photo2.jpg +locker_room/locker_room_google_0112.jpg +mall/lakeside_center_42.jpg +concert_hall/milwaukeetheatre.jpg +nursery/babyroomshelves.jpg +videostore/videoclub_09_19_flickr.jpg +airport_inside/airport_inside_0107.jpg +locker_room/locker_room_google_0066.jpg +toystore/speelgoed_45_01_flickr.jpg +closet/MasterSuiteLaminateWI.jpg +airport_inside/airport_inside_0163.jpg +hairsalon/mfotos_peluqueria_001.jpg +church_inside/P1000169.jpg +buffet/buffetmetro_533.jpg +stairscase/N457080.jpg +movietheater/cine_sony.jpg +gameroom/room397.jpg +office/ins26.jpg +fastfood_restaurant/quiznos020508a.jpg +bedroom/indoor_0215.jpg +prisoncell/Jail_DSC00095.jpg +church_inside/kirche_innen_2.jpg +dentaloffice/dental_office_20_20_altavista.jpg +library/New_York_Public_Library5.jpg +buffet/1600963137_2dc4c3f005.jpg +laundromat/lavanderia_19_19_flickr.jpg +hairsalon/salon1.jpg +deli/deli_121_18_flickr.jpg +mall/abasto_mall.jpg +tv_studio/tv_studio_03_03_altavista.jpg +laundromat/maytagLAUNDNORTHVIEW.jpg +office/despacho_medico_2.jpg +bar/bar_0081.jpg +classroom/classroom12.jpg +deli/deli_27_12_yahoo.jpg +jewelleryshop/rewardsstore2_27_.jpg +nursery/photo_5353117.jpg +dining_room/int294.jpg +greenhouse/invernadero01.jpg +jewelleryshop/img_5204_36_.jpg +bookstore/Libreria_27_16_altavista.jpg +laundromat/lavanderia_60_01_flickr.jpg +florist/florist_28_12_flickr.jpg +operating_room/operating_room_25_02_altavista.jpg +fastfood_restaurant/Subway1.jpg +gym/gym11.jpg +kindergarden/classroom_october.jpg +cloister/claustro_san_francisco.jpg +hospitalroom/Afghan_Hospital_Room_sized.jpg +movietheater/movietheater_google_0057.jpg +church_inside/FreeFoto_castle_30_36.jpg +bakery/boulangerie_32_16_yahoo.jpg +movietheater/projection_hippo_1__59.jpg +office/office10.jpg +bowling/bowling_0055.jpg +nursery/2502867662_5fc6c074f4.jpg +tv_studio/tv_studio4_117_.jpg +library/meura1.jpg +pantry/pantry_97_19_flickr.jpg +classroom/classroom86.jpg +bookstore/bookstore_02.jpg +garage/Garage104_w560h420.jpg +shoeshop/ZAPATERIA43.jpg +trainstation/gare_63_02_flickr.jpg +hairsalon/Salon254.jpg +kitchen/int362.jpg +fastfood_restaurant/rss_d.jpg +jewelleryshop/sjstore2_28_.jpg +shoeshop/gallery342.jpg +gym/gimnasio_17_08_altavista.jpg +restaurant/restaurant_31_10_altavista.jpg +garage/Garage_After.jpg +studiomusic/60984.jpg +gameroom/AT_01_6B_5479_25_l.jpg +kitchen/int423.jpg +children_room/playroom_1_34_.jpg +classroom/sala_de_clases.jpg +tv_studio/dscn9703_41_.jpg +artstudio/art_painting_studio_01_13_altavista.jpg +grocerystore/zoom_1_1188095.jpg +dining_room/int468.jpg +subway/subway_0172.jpg +kitchen/cdmc1146.jpg +children_room/playroom6_31_.jpg +inside_bus/inside_bus_016.jpg +nursery/mayalau_robot_nursery.jpg +operating_room/surgery_room_08_16_altavista.jpg +bedroom/indoor_0113.jpg +church_inside/kirche_innen.jpg +bowling/bowling_0027.jpg +meeting_room/c20.jpg +jewelleryshop/joyeria_01g.jpg +inside_bus/inside_bus_007.jpg +warehouse/warehouse_0040.jpg +grocerystore/supermarkt_fr.jpg +locker_room/locker_room_google_0046.jpg +tv_studio/21lg_15_.jpg +closet/closet_2.jpg +gym/Gym2_png.jpg +poolinside/pool_inside_24_13_altavista.jpg +greenhouse/Greenhouse_2.jpg +stairscase/CA_97_1_289_24A_l.jpg +dentaloffice/dental_office_01_04_altavista.jpg +bowling/bowling_0166.jpg +dining_room/dining41.jpg +bakery/panaderia_49_21_yahoo.jpg +closet/closet01.jpg +subway/subway_0224.jpg +auditorium/amphi01_1__77.jpg +dentaloffice/dental_office_49_03_altavista.jpg +library/library05.jpg +casino/casino_0049.jpg +pantry/11.jpg +bathroom/room315.jpg +concert_hall/KnightHallMiami460.jpg +inside_bus/inside_bus_011.jpg +movietheater/auditorium609_37.jpg +inside_subway/inside_subway_0259.jpg +bathroom/b1.jpg +closet/Clothed_Closet_photo_300px_22Mar00.jpg +inside_subway/inside_subway_0191.jpg +poolinside/poolMain.jpg +trainstation/gare_100_19_flickr.jpg +dentaloffice/dental_office_17_06_altavista.jpg +lobby/sLobby16.jpg +concert_hall/concert_Hall.jpg +florist/florist_19_14_altavista.jpg +church_inside/283421883_0a184e7b4d.jpg +library/112630581_f21ae30872.jpg +closet/Master-Closet.jpg +jewelleryshop/20040407a01.jpg +deli/deli_59_09_flickr.jpg +gameroom/317_salle_de_jeux.jpg +hospitalroom/Hospital_Room_30.jpg +meeting_room/n457004.jpg +cloister/2474769210_7847df4894.jpg +bakery/panaderia_49_08_yahoo.jpg +movietheater/cinema2_47.jpg +bakery/boulangerie_01_05_yahoo.jpg +nursery/url.jpg +shoeshop/zapateria_07_11_flickr.jpg +garage/XF_HOMES_400.jpg +laundromat/LV_02_03_9714_21_l.jpg +meeting_room/n457037.jpg +greenhouse/Fabricated_Metal_Plastic_Tube_Greenhouse.jpg +subway/subway_0337.jpg +laboratorywet/laboratorio_quimica_12_15_altavista.jpg +meeting_room/conf02.jpg +studiomusic/DSCN3237M.jpg +clothingstore/Bookstore_Interior_rdax_90.jpg +lobby/sLobby10.jpg +bar/bar_0066.jpg +classroom/Laura_Lander_SMART_Classroom.jpg +gameroom/Gameroom7.jpg +airport_inside/airport_inside_0253.jpg +bedroom/indoor_0267.jpg +church_inside/metropolitana_160_23_flickr.jpg +laundromat/lavanderia_09_22_flickr.jpg +livingroom/iship.jpg +studiomusic/DCOM_Music_lg_nws_2164_5043.jpg +videostore/videostore2.jpg +nursery/babys_room_crib_714240.jpg +bakery/boulangerie_31_05_yahoo.jpg +clothingstore/Lulas_017_VG6D.jpg +bar/bar_0372.jpg +dining_room/d13.jpg +clothingstore/home0001.jpg +kitchen/img_6613.jpg +winecellar/bodega_44_19_flickr.jpg +nursery/baby_room_sm.jpg +waitingroom/icu_waiting_room_1.jpg +meeting_room/conf27.jpg +office/office8.jpg +grocerystore/MG_56_belo_grocery_2.jpg +bowling/bowling_0088.jpg +bedroom/b12.jpg +gameroom/OR_02_05_0481_11_l.jpg +museum/museo_161_02_flickr.jpg +nursery/perfect_nursery_shot.jpg +bakery/new_bakery_16_09_altavista.jpg +nursery/idkidsc0301_png.jpg +cloister/cloister_med.jpg +church_inside/243088.jpg +florist/florist_41_11_flickr.jpg +computerroom/informatica.jpg +videostore/videotheque_02_01_flickr.jpg +dentaloffice/dentaloffice05.jpg +nursery/chambre_enfant_avant.jpg +bedroom/bed2.jpg +laboratorywet/wet_lab_46_11_altavista.jpg +buffet/413438433_cdcef3db3e.jpg +poolinside/piscina_cubierta_02_13_altavista.jpg +clothingstore/d0001.jpg +gym/SalleMuscu.jpg +toystore/toys_store_01_08_altavista.jpg +hospitalroom/causulty_room.jpg +movietheater/movie-theater.jpg +mall/Rome_Shopping_mall.jpg +warehouse/warehouse_0056.jpg +museum/museum_41_03_altavista.jpg +office/ph_02_03_4680_35_l.jpg +pantry/62277822_SbYbMw5F.jpg +kindergarden/classroom233.jpg +stairscase/HO_00_04_5808_09A_l.jpg +clothingstore/1.jpg +greenhouse/gewaechshaus_4.jpg +restaurant_kitchen/restaurant_kitchen_google_0022.jpg +prisoncell/Photo28120.jpg +jewelleryshop/1ct1.jpg +videostore/videoclub_04_17_flickr.jpg +prisoncell/large_new-jail-cell.jpg +waitingroom/waiting_room_32_20_altavista.jpg +nursery/313886124_6ed775cde1.jpg +computerroom/biblioteca1.jpg +movietheater/movietheater_google_0051.jpg +church_inside/steinh2.jpg +classroom/CLASSE3.jpg +airport_inside/airport_inside_0176.jpg +dentaloffice/dentaire_27_16_altavista.jpg +museum/museum_38_03_altavista.jpg +pantry/despensa_02_20_altavista.jpg +operating_room/operating_room_01_20_altavista.jpg +buffet/413223473_be5a4b6fc3.jpg +nursery/babyroom003.jpg +clothingstore/storesshot2.jpg +museum/museo_01_06_flickr.jpg +deli/deli_10_13_altavista.jpg +corridor/c8.jpg +fastfood_restaurant/dsc00878smdj1.jpg +garage/Garage inside.jpg +cloister/claustre.jpg +gym/gym_21_01_altavista.jpg +warehouse/warehouse_0204.jpg +hospitalroom/hospital_room_01_16_altavista.jpg +restaurant/restaurante_04_20_altavista.jpg +shoeshop/11511434951.jpg +museum/museo_33_14_altavista.jpg +casino/casino_0352.jpg +garage/pic.jpg +mall/supermarkt.jpg +florist/floristeria.jpg +locker_room/locker_room_google_0013.jpg +stairscase/PA_07_01_1000_38_m.jpg +pantry/7.jpg +gym/gym_01_01_altavista.jpg +laboratorywet/wet_lab_10_15_altavista.jpg +lobby/09_Vestibulo.jpg +restaurant/restaurante_30_02_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0107.jpg +hairsalon/DSCN3259_modifie_1.jpg +livingroom/indoor_0260.jpg +studiomusic/studio_a.jpg +livingroom/easyst013.jpg +laboratorywet/wet_lab_34_05_altavista.jpg +restaurant/IMG_0350.jpg +restaurant_kitchen/restaurant_kitchen_google_0011.jpg +tv_studio/lakeview_desk_013_50_.jpg +prisoncell/jail_hallway.jpg +casino/casino_0035.jpg +stairscase/AT_01_6B_5479_32_l.jpg +deli/deli_10_17_yahoo.jpg +gameroom/SD_00_06_51275_24_l.jpg +children_room/VR_06_01_1000_95_l.jpg +bar/bar_0021.jpg +videostore/videoteca_07_03_flickr.jpg +poolinside/room154.jpg +locker_room/locker_room_google_0019.jpg +grocerystore/Bashas_Int2_.jpg +poolinside/0310_piscines_1.jpg +fastfood_restaurant/KFC_Interior_1.jpg +restaurant_kitchen/restaurant_kitchen_google_0036.jpg +closet/0207_closet.jpg +shoeshop/pmv810_en.jpg +subway/subway_0117.jpg +bowling/bowling_0041.jpg +clothingstore/bookstore_photo.jpg +stairscase/treppe_neu_g.jpg +studiomusic/control.jpg +winecellar/wine_cellar_02_13_altavista.jpg +waitingroom/salon_accueil.jpg +concert_hall/concert_hall_lg.jpg +concert_hall/segerstrom_concert_hall_01_1.jpg +gameroom/sala_de_juegos_14_06_altavista.jpg +laundromat/lavanderia_52_19_flickr.jpg +clothingstore/Mvc_006f.jpg +fastfood_restaurant/blimpies.jpg +kindergarden/DSC01105.jpg +restaurant_kitchen/restaurant_kitchen_google_0077.jpg +casino/casino_0002.jpg +inside_subway/inside_subway_0286.jpg +gameroom/AT_05_05_5000_43_l.jpg +gym/int837.jpg +inside_subway/metropolitana_19_20_flickr.jpg +bedroom/indoor_0285.jpg +jewelleryshop/silver_shop_11.jpg +computerroom/aut_0003.jpg +livingroom/living69.jpg +pantry/pantry_12_11_altavista.jpg +subway/subway_0061.jpg +mall/mall23.jpg +casino/casino_0034.jpg +operating_room/operating_room_11_12_altavista.jpg +church_inside/as_kirche.jpg +elevator/elevator_google_0069.jpg +livingroom/l10.jpg +subway/subway_0323.jpg +dining_room/n190003.jpg +winecellar/bodega_107_06_flickr.jpg +clothingstore/100_0086.jpg +artstudio/famous_painters_studio_01_12_altavista.jpg +tv_studio/2d_1_.jpg +mall/HoustonGalleria_XMas.jpg +pantry/pantry_149_06_flickr.jpg +pantry/WhiteWIPantry.jpg +waitingroom/waiting.jpg +dining_room/or_02_01_0743_30_l.jpg +fastfood_restaurant/32_The_Angkor_Fast_Food_Restaurant.jpg +dentaloffice/dental_office_01_12_altavista.jpg +stairscase/N28.jpg +dentaloffice/dentista_123_04_flickr.jpg +poolinside/pool_inside_26_10_altavista.jpg +bedroom/indoor_0289.jpg +dentaloffice/dental_office_19_20_altavista.jpg +florist/florist_32_09_flickr.jpg +trainstation/train_station_32_10_altavista.jpg +deli/deli_59_06_flickr.jpg +restaurant/bistro_49_08_altavista.jpg +museum/museo_03_01_flickr.jpg +tv_studio/philhulettlecture_95_.jpg +computerroom/IM000834.jpg +bar/bar_0594.jpg +kindergarden/Lower_Kindergarten.jpg +children_room/reading_circle_time_37_.jpg +buffet/dessert_buffet17_11_2007_9_14_28_PM.jpg +bar/bar_0483.jpg +office/int61.jpg +bakery/panetteria_13_16_altavista.jpg +dentaloffice/IMG_0130_jpg_450x450.jpg +locker_room/locker_room_google_0003.jpg +mall/mall25.jpg +bathroom/bath303.jpg +jewelleryshop/garnetsDSC00059.jpg +gym/GymInt1.jpg +hairsalon/salon345.jpg +restaurant_kitchen/restaurant_kitchen_google_0034.jpg +closet/closet.jpg +jewelleryshop/boutiqueoo.jpg +pantry/03_pantry_storage_organization_lg.jpg +bedroom/bedroom27.jpg +winecellar/cave_champagne_07_11_altavista.jpg +museum/museum_02_09_altavista.jpg +toystore/jugueteria_02_08_altavista.jpg +poolinside/schwimmbad333.jpg +toystore/jugueteria_21_03_flickr.jpg +dining_room/dining22.jpg +operating_room/surgery_room_09_10_altavista.jpg +restaurant/home_12.jpg +livingroom/living29.jpg +toystore/jugueteria_05_03_flickr.jpg +dentaloffice/dental_office_21_02_altavista.jpg +bathroom/bath166.jpg +cloister/1526793682_7d53006762.jpg +garage/SantaRosaGarage_small_.jpg +meeting_room/n457021.jpg +videostore/P1020026.jpg +kindergarden/lokilani_room_1.jpg +concert_hall/Music_Concert_Hall_3.jpg +office/or_02_03_0212_11_l.jpg +bookstore/Librairie_02.jpg +children_room/PO_06_02_2000_71_l.jpg +classroom/P1010077.jpg +cloister/claustroreyes.jpg +closet/child2.jpg +videostore/videoclub_04_09_flickr.jpg +children_room/dramatic_play_room_42_.jpg +cloister/Pamplona_catedral_claustro.jpg +laundromat/MVC_003F.jpg +movietheater/movietheater_google_0052.jpg +livingroom/roomscan17.jpg +buffet/322497200_a22d028e6f.jpg +waitingroom/waitingarea.jpg +gym/gimnasio_09_15_altavista.jpg +warehouse/warehouse_0371.jpg +greenhouse/2026_2006_Grimm_s_Gardens_Greenhouse.jpg +jewelleryshop/jewelry_store_interior_design_751036_19_.jpg +elevator/elevator_google_0048.jpg +meeting_room/c10.jpg +meeting_room/n457013.jpg +buffet/NOR_BuffetFood_300x225_300x225.jpg +hairsalon/11549402671.jpg +winecellar/wine_storage_26_12_altavista.jpg +pantry/pantry_85_06_flickr.jpg +deli/deli_06_15_yahoo.jpg +library/ins22.jpg +greenhouse/greenhouse8.jpg +laundromat/oaklandlaundromat_main_full.jpg +gym/gimnasio_167_12_flickr.jpg +garage/Garage_3_view_3_interior.jpg +jewelleryshop/joyeria_rometsch03.jpg +bakery/boulangerie_36_01_altavista.jpg +operating_room/surgery_room_17_15_altavista.jpg +bar/bar_0261.jpg +mall/ins23.jpg +waitingroom/WtRoomJan06.jpg +bedroom/indoor_0338.jpg +restaurant_kitchen/restaurant_kitchen_google_0090.jpg +mall/wafi_shopping_center.jpg +trainstation/gare_80_11_flickr.jpg +clothingstore/cupido_vestiti.jpg +nursery/cotsl.jpg +shoeshop/zapateria10.jpg +concert_hall/baxter3.jpg +shoeshop/zapateria_23_03_flickr.jpg +elevator/elevator_google_0024.jpg +clothingstore/77_bookstore.jpg +subway/metropolitana_20_02_altavista.jpg +cloister/claustro34.jpg +meeting_room/conf22.jpg +deli/deli_98_21_flickr.jpg +computerroom/COMPUTER_ROOM2.jpg +museum/museum_20_19_altavista.jpg +auditorium/3_10_08auditorium2_1__7.jpg +laboratorywet/laboratorio_quimica_10_01_altavista.jpg +pantry/Close_Mesh_Pantry_w_Drawer.jpg +subway/subway_0310.jpg +warehouse/warehouse_0186.jpg +church_inside/st_anne_church_budapest_inside.jpg +auditorium/auditorium3_l_1__111.jpg +garage/garage66.jpg +library/biblio_livre.jpg +livingroom/living88.jpg +auditorium/auditorium2_l_1__108.jpg +bedroom/linama_014.jpg +deli/new_deli_39_04_altavista.jpg +movietheater/movietheater_google_0037.jpg +dentaloffice/dental_office_11_02_altavista.jpg +laundromat/laundromat_shark_perspectiv.jpg +casino/casino_0025.jpg +church_inside/blasii_kirche_nordhausen.jpg +corridor/couloir06.jpg +greenhouse/Gewaechshaus.jpg +laundromat/kachel10.jpg +subway/subway_0066.jpg +clothingstore/boutique6.jpg +computerroom/computerroom04.jpg +classroom/classroom7.jpg +concert_hall/Overture_Hall_1.jpg +shoeshop/zapateria_26_20_flickr.jpg +florist/floreria_04_03_flickr.jpg +restaurant/restaurant_11_07_altavista.jpg +toystore/toys_store_12_18_altavista.jpg +meeting_room/c13.jpg +closet/MasterSuiteLrg.jpg +greenhouse/celosie_02.jpg +mall/crocker_galleria_735089.jpg +restaurant_kitchen/restaurant_kitchen_google_0051.jpg +office/office20.jpg +livingroom/n14m.jpg +movietheater/sallecine_72.jpg +dentaloffice/dentista_45_10_altavista.jpg +operating_room/surgery_room_07_13_altavista.jpg +kitchen/kitchenblack.jpg +inside_bus/inside_bus_087.jpg +jewelleryshop/2711107.jpg +gameroom/8a_gameroom2.jpg +greenhouse/ESTUFA.jpg +bathroom/bath181.jpg +grocerystore/shop12.jpg +poolinside/pool_inside_01_14_altavista.jpg +subway/subway_0286.jpg +kitchen/cdmc1170.jpg +operating_room/operating_room_05_20_altavista.jpg +florist/florist_02_06_altavista.jpg +inside_bus/inside_bus_059.jpg +kindergarden/Preschool_classroom78.jpg +hospitalroom/IMG_0625.jpg +poolinside/Pool_inside_birdcage_1641Pas2202.jpg +winecellar/bodega_100_24_flickr.jpg +library/la_bibliotheque_de_la_tour_du_valat.jpg +bathroom/indoor_0342.jpg +inside_subway/inside_subway_0057.jpg +buffet/Maritimo_Restaurant_Buffet.jpg +church_inside/metropolitana_83_13_flickr.jpg +dentaloffice/office3.jpg +gym/gym04.jpg +bathroom/indoor_0465.jpg +operating_room/surgery_room_17_07_altavista.jpg +poolinside/buddysbi.jpg +closet/lamCloset.jpg +computerroom/Room_2520116E.jpg +auditorium/amphitheatre_bastille_1__23.jpg +bar/bar_0296.jpg +laundromat/100_0125kleinNEW.jpg +movietheater/findingnemo1_480x320_1__20.jpg +kindergarden/DSC000552.jpg +florist/tienda_005.jpg +gym/southwaterGym.jpg +florist/floristeria1.jpg +kitchen/int34.jpg +restaurant/salle_restaurant2.jpg +inside_subway/inside_subway_0055.jpg +gym/biosite_gym.jpg +artstudio/art_painting_studio_04_20_altavista.jpg +bakery/bakery_19_10_yahoo.jpg +church_inside/CatholicChurchInteriorCapeMayNJ.jpg +pantry/WhiteRaisedPanelRI_Pantry.jpg +prisoncell/1_jail_cell.jpg +inside_bus/inside_bus_006.jpg +meeting_room/c2.jpg +airport_inside/airport_inside_0294.jpg +bar/bar_0011.jpg +church_inside/church13.jpg +closet/master_SCI132.jpg +inside_subway/inside_subway_0243.jpg +dentaloffice/dental_room.jpg +laboratorywet/Wet_lab.jpg +winecellar/1184099831156_02.jpg +florist/florist_02_02_altavista.jpg +buffet/buffet_FoodLine1.jpg +poolinside/lignon_piscine.jpg +bedroom/b2.jpg +bakery/best_bakery_02_07_altavista.jpg +hairsalon/peluqueria.jpg +waitingroom/waiting_room_closeup_1.jpg +greenhouse/greenhouse842.jpg +subway/subway_0272.jpg +cloister/cloister1ms.jpg +movietheater/image_preview_1__51.jpg +nursery/colorful_mod_nursery_amazing_wall_painting_techniques_8178.jpg +mall/Moscow2006ssIMG_7450.jpg +clothingstore/Paris_Boutique_Inside_Store.jpg +florist/florist_35_17_altavista.jpg +shoeshop/pht_mag_02.jpg +waitingroom/sala_de_espera_04_16_altavista.jpg +garage/here_is_a_closer_look_inside_the_garage.jpg +locker_room/locker_room_google_0180.jpg +bedroom/IMG_1555.jpg +stairscase/hall117.jpg +bowling/bowling_0059.jpg +clothingstore/wares2.jpg +mall/CP67791.jpg +mall/800px_Interior_mall_of_america.jpg +deli/deli_131_13_flickr.jpg +mall/mall08.jpg +fastfood_restaurant/guest3.jpg +inside_bus/inside_bus_073.jpg +movietheater/rade88fb_1__60.jpg +warehouse/warehouse_0161.jpg +deli/new_deli_03_01_altavista.jpg +dining_room/dining003.jpg +library/fibiba1.jpg +restaurant_kitchen/restaurant_kitchen_google_0049.jpg +florist/anaisflor480.jpg +bakery/bakery_51_21_yahoo.jpg +studiomusic/studio14.jpg +florist/florist_interior.jpg +restaurant_kitchen/restaurant_kitchen_google_0064.jpg +bar/bar_0106.jpg +children_room/our_house_043_48_.jpg +closet/closet_after_2.jpg +kitchen/room3.jpg +restaurant_kitchen/restaurant_kitchen_google_0080.jpg +bathroom/indoor_0493.jpg +restaurant_kitchen/restaurant_kitchen_google_0102.jpg +bookstore/JR64407_ArtProst_bookstore.jpg +operating_room/surgery_room_43_11_altavista.jpg +bedroom/b26.jpg +waitingroom/general_waiting_area.jpg +restaurant/int131.jpg +shoeshop/zapateria_15_23_flickr.jpg +cloister/cc_cloister_3.jpg +closet/Closet_main_page_415x319.jpg +inside_subway/inside_subway_0040.jpg +bedroom/indoor_0331.jpg +bar/bar_0180.jpg +livingroom/l2.jpg +church_inside/metropolitana_41_08_flickr.jpg +meeting_room/c16.jpg +studiomusic/ControlRoomSm.jpg +tv_studio/pb030044_53_.jpg +nursery/15806.jpg +studiomusic/int769.jpg +inside_subway/inside_subway_0146.jpg +bar/bar_0364.jpg +hospitalroom/Hospital_room_ubt.jpg +bar/bar_0398.jpg +restaurant_kitchen/restaurant_kitchen_google_0085.jpg +airport_inside/airport_inside_0036.jpg +hospitalroom/int180.jpg +hospitalroom/int197.jpg +computerroom/biblioteca_salamultimedia_g.jpg +concert_hall/Feature0202_04x.jpg +bowling/bowling_0208.jpg +winecellar/wine_cellar_48_12_altavista.jpg +computerroom/computer_room.jpg +kitchen/kitchen014.jpg +meeting_room/pz707.jpg +elevator/elevator_google_0080.jpg +bakery/bakery_25_11_yahoo.jpg +mall/main_boutique_b.jpg +movietheater/6_voyager_magazine_256322_1__12.jpg +computerroom/computerroom01.jpg +laboratorywet/laboratorio_quimica_03_15_altavista.jpg +trainstation/gare_22_22_flickr.jpg +inside_subway/inside_subway_0401.jpg +bar/bar_0298.jpg +mall/mall10.jpg +toystore/jugueteria_20_11_flickr.jpg +artstudio/artist_studio_01_05_altavista.jpg +elevator/elevator_google_0029.jpg +kindergarden/daycare03142008062.jpg +office/n457026.jpg +closet/MasterSuiteBoyBR.jpg +bedroom/b10.jpg +dining_room/int553.jpg +operating_room/surgery_room_16_19_altavista.jpg +operating_room/operating_room_06_10_altavista.jpg +subway/subway_0181.jpg +bakery/boulangerie_12_13_yahoo.jpg +classroom/NFA128_classroom.jpg +studiomusic/6176_2.jpg +bathroom/b7.jpg +airport_inside/airport_inside_0008.jpg +buffet/seafoodbuffet.jpg +inside_subway/inside_subway_0094.jpg +inside_subway/metropolitana_39_04_flickr.jpg +waitingroom/photo_office_tour_1.jpg +airport_inside/airport_inside_0225.jpg +casino/casino_0449.jpg +restaurant/restaurant_27_10_altavista.jpg +cloister/Claustro_de_San_Isidoro_0.jpg +inside_subway/inside_subway_0167.jpg +kindergarden/rocking_chair_area_large2.jpg +hospitalroom/DSC01602.jpg +meeting_room/n457006.jpg +inside_subway/inside_subway_0339.jpg +dentaloffice/dentista_94_08_flickr.jpg +fastfood_restaurant/x_panda.jpg +bowling/bowling_0136.jpg +hairsalon/387_photoCommerce_1.jpg +tv_studio/estudio_fabricio_ojeda_87_.jpg +laboratorywet/wet_lab_16_03_altavista.jpg +garage/Hafdal_Garage_008crop.jpg +laboratorywet/laboratorio_quimica_01_13_altavista.jpg +shoeshop/zapateria3.jpg +bookstore/bookstore_21_07_flickr.jpg +hairsalon/Photo6.jpg +office/int230.jpg +warehouse/warehouse_0130.jpg +bowling/bowling_0188.jpg +library/int91.jpg +bedroom/b16.jpg +bakery/panaderia_35_06_yahoo.jpg +church_inside/innenansicht_kirche.jpg +fastfood_restaurant/dunkin_donuts.jpg +deli/deli_81_07_flickr.jpg +garage/garage4_1.jpg +grocerystore/grocery_store01.jpg +hospitalroom/hospital_room_33_10_altavista.jpg +pantry/despensa_11_18_flickr.jpg +corridor/couloir.jpg +inside_bus/inside_bus_038.jpg +locker_room/locker_room_google_0164.jpg +garage/bens_garage.jpg +greenhouse/04_06Greenhouse.jpg +subway/subway_0270.jpg +deli/deli_80_01_flickr.jpg +movietheater/sallevideok.jpg +prisoncell/JailCell2.jpg +warehouse/warehouse_0101.jpg +studiomusic/music_studio1.jpg +livingroom/living25.jpg +grocerystore/800px_Fett_supermarkt.jpg +shoeshop/zapateria_09_04_flickr.jpg +laboratorywet/AA049672.jpg +studiomusic/room222.jpg +operating_room/operating_table_05_02_altavista.jpg +meeting_room/conf21.jpg +bedroom/indoor_0448.jpg +shoeshop/zapateria_02_22_flickr.jpg +waitingroom/Waiting_Area.jpg +classroom/CEC_208_classroom.jpg +restaurant/restaurant_18_19_altavista.jpg +classroom/classroom_full.jpg +poolinside/piscina_cubierta_10_12_altavista.jpg +bedroom/b21.jpg +classroom/dark_empty_classroom.jpg +hairsalon/salon3-gr.jpg +meeting_room/tc_conference_room.jpg +studiomusic/studio99.jpg +waitingroom/waiting_room_39_03_altavista.jpg +tv_studio/plato_de_television_01_11_altavista.jpg +clothingstore/boutique55.jpg +gym/gimnasio_22_05_altavista.jpg +inside_bus/inside_bus_098.jpg +kindergarden/two2.jpg +deli/deli_08_12_yahoo.jpg +locker_room/locker_room_google_0045.jpg +laboratorywet/laboratorio_quimica_11_01_altavista.jpg +poolinside/pool_inside.jpg +winecellar/bodega_102_04_flickr.jpg +poolinside/2141886.jpg +fastfood_restaurant/BurgerKing.jpg +corridor/p1010062_c.jpg +jewelleryshop/interior.jpg +office/o10.jpg +winecellar/wine_cellar_46_03_altavista.jpg +elevator/elevator_google_0062.jpg +mall/room402.jpg +waitingroom/sample18.jpg +bookstore/bookstore_24_24_flickr.jpg +corridor/c2.jpg +bakery/The_12_Bakery.jpg +trainstation/train_station_15_19_altavista.jpg +computerroom/Our_Computer_Room_2007.jpg +meeting_room/c3.jpg +church_inside/metropolitana_102_20_flickr.jpg +dining_room/dining29.jpg +buffet/buffet07.jpg +meeting_room/c12.jpg +casino/casino_0320.jpg +gym/gimnasio_17_07_altavista.jpg +pantry/pantry_35_12_flickr.jpg +bar/bar_0374.jpg +clothingstore/dresses.jpg +library/Bibliotheque02.jpg +hospitalroom/IMG_1026.jpg +computerroom/lab2.jpg +greenhouse/invernadero_1111.jpg +stairscase/N457073.jpg +computerroom/aula_informat1.jpg +elevator/elevator_google_0041.jpg +greenhouse/serre_4.jpg +dining_room/indoor_0582.jpg +buffet/buffet04.jpg +bookstore/bookstore_41_02_altavista.jpg +casino/casino_0470.jpg +restaurant/restaurant_10_09_altavista.jpg +corridor/n457055.jpg +kitchen/indoor_0558.jpg +stairscase/N457090.jpg +closet/WH_WI.jpg +airport_inside/airport_inside_0064.jpg +winecellar/bodega_88_08_flickr.jpg +dentaloffice/dental_office_02_12_altavista.jpg +hospitalroom/hospital_room_sm.jpg +meeting_room/conf24.jpg +restaurant/restaurante_04_14_altavista.jpg +laundromat/lavanderia_106_03_flickr.jpg +airport_inside/airport_inside_0032.jpg +warehouse/warehouse_0325.jpg +locker_room/locker_room_google_0092.jpg +nursery/chambrebebe2.jpg +prisoncell/prison_cell_04_01_altavista.jpg +cloister/london_westminster_cloister.jpg +office/n457029.jpg +operating_room/hospital room_30_06_altavista.jpg +toystore/jugueteria_15_13_flickr.jpg +church_inside/kirche_01.jpg +library/Dsc00613_3.jpg +locker_room/locker_room_google_0192.jpg +bookstore/Libreria_46_17_altavista.jpg +lobby/Dolphin_01l_lobby_fountain.jpg +greenhouse/greenhouse_652.jpg +kitchen/cdmc1119.jpg +clothingstore/KIK.jpg +kitchen/kitchen102.jpg +office/office13.jpg +deli/deli_120_17_flickr.jpg +grocerystore/1760032753_ceceee30e7.jpg +pantry/White_Pantry_lg_logo.jpg +shoeshop/fashion.jpg +airport_inside/airport_inside_0337.jpg +bakery/boulangerie_06_16_yahoo.jpg +elevator/elevator_google_0071.jpg +restaurant/restaurante_37_06_altavista.jpg +inside_bus/inside_bus_032.jpg +lobby/recibidor10.jpg +waitingroom/surroundings1_large.jpg +casino/casino_0003.jpg +elevator/elevator_google_0034.jpg +grocerystore/supermarche1.jpg +lobby/sLobby15.jpg +lobby/lobby25.jpg +locker_room/locker_room_google_0128.jpg +computerroom/salle_informatique2.jpg +concert_hall/csi.jpg +restaurant_kitchen/restaurant_kitchen_google_0093.jpg +auditorium/auditorium015_103.jpg +stairscase/N190092.jpg +meeting_room/conf09.jpg +office/room472.jpg +hairsalon/aula_parrucchiere.jpg +museum/metropolitana_11_01_altavista.jpg +pantry/despensa_140_11_flickr.jpg +computerroom/biblioteca.jpg +locker_room/locker_room_google_0191.jpg +computerroom/DSCN1030.jpg +jewelleryshop/banner55.jpg +laundromat/laundry_II.jpg +restaurant_kitchen/restaurant_kitchen_google_0038.jpg +bookstore/bookstore_45_11_flickr.jpg +kindergarden/Classroom_pic_8_23_07_002.jpg +deli/deli_02_03_yahoo.jpg +artstudio/art_painting_studio_16_06_altavista.jpg +closet/Open_Closet.jpg +warehouse/warehouse_0078.jpg +greenhouse/20070417klpcnatun_229_Ies_SCO.jpg +studiomusic/Sala_de_actuacion.jpg +elevator/elevator_google_0033.jpg +meeting_room/conf10.jpg +deli/deli_106_23_flickr.jpg +greenhouse/greenhouse167.jpg +waitingroom/waiting_room_36_19_altavista.jpg +bedroom/indoor_0431.jpg +bowling/bowling_0209.jpg +clothingstore/american_shorts.jpg +mall/A_Grand_240905.jpg +winecellar/sacristia.jpg +airport_inside/airport_inside_0391.jpg +buffet/1430398034_7ab3fed17f.jpg +kindergarden/100_0034.jpg +warehouse/warehouse_0486.jpg +clothingstore/0009.jpg +concert_hall/11_19_2005_NGL_19opacHALL_GB61OCUJ6_1.jpg +nursery/visuel2.jpg +restaurant_kitchen/restaurant_kitchen_google_0062.jpg +laundromat/laundry_room_large.jpg +livingroom/lv_02_04_10917_04a_l.jpg +locker_room/locker_room_google_0067.jpg +pantry/pantry_15.jpg +tv_studio/tv_studio_32_12_altavista.jpg +concert_hall/Concert_Hall_1.jpg +shoeshop/2574_1914_DSCN2351.jpg +artstudio/artist_studio_49_17_altavista.jpg +bowling/bowling_0177.jpg +corridor/p1010074_c.jpg +inside_bus/inside_bus_040.jpg +gameroom/salle_jeux.jpg +warehouse/warehouse_0021.jpg +inside_bus/inside_bus_069.jpg +nursery/1323nursery5_med.jpg +library/BIB_bibliothek.jpg +studiomusic/int805.jpg +subway/subway_0219.jpg +videostore/videoteca_06_17_flickr.jpg +church_inside/churchinside01.jpg +dining_room/d11.jpg +dentaloffice/dentista_99_23_flickr.jpg +deli/deli_28_07_altavista.jpg +clothingstore/The_Boutique.jpg +office/n457050.jpg +meeting_room/c5.jpg +videostore/videoclub_29_18_altavista.jpg +winecellar/wine_storage_04_03_altavista.jpg +bakery/boulangerie_10_10_yahoo.jpg +library/Library_P2150016.jpg +shoeshop/zapateria.jpg +casino/casino_0302.jpg +toystore/toys_store_16_05_altavista.jpg +concert_hall/Concert_Hall2_1.jpg +hairsalon/salondecoiffure9215oq7.jpg +library/Bibliotheque6.jpg +livingroom/smallsp008.jpg +bookstore/librairie2_g.jpg +florist/flower_shop_spring_2006.jpg +kindergarden/classroom_north_bmp.jpg +buffet/buffet02.jpg +winecellar/wine_cellar_05_16_altavista.jpg +computerroom/Computer_Classroom.jpg +operating_room/operating_room_49_20_altavista.jpg +bowling/bowling_0061.jpg +kindergarden/tots6.jpg +church_inside/metropolitana_115_18_flickr.jpg +corridor/n190039.jpg +concert_hall/Concert Hall.jpg +inside_bus/inside_bus_092.jpg +kitchen/cdmc1299.jpg +church_inside/metropolitana_151_07_flickr.jpg +hairsalon/hair_salon1.jpg +lobby/lobby51.jpg +bathroom/IMG_1252.jpg +locker_room/locker_room_google_0050.jpg +trainstation/gare_03_12_altavista.jpg +dining_room/cdmc1295.jpg +clothingstore/boutique42.jpg +dining_room/dsc00627.jpg +nursery/baby_room_decoration_lg.jpg +shoeshop/zapato_2.jpg +studiomusic/blossom_music_studio_2.jpg +office/homeoff014.jpg +closet/075381028905.jpg +fastfood_restaurant/Camille_s_027.jpg +locker_room/locker_room_google_0141.jpg +church_inside/metropolitana_149_08_flickr.jpg +meeting_room/room451.jpg +shoeshop/zapateria_16_08_flickr.jpg +bookstore/bookstore_57_04_flickr.jpg +closet/closet_4.jpg +shoeshop/shoes_shop_01_01_altavista.jpg +elevator/elevator_google_0038.jpg +livingroom/Living31.jpg +artstudio/artist_studio_41_05_altavista.jpg +tv_studio/estudio_de_television_01_02_flickr.jpg +buffet/e02_buffet.jpg +bookstore/Librairie_48_15_altavista.jpg +dentaloffice/1730803206_3f16248ecb.jpg +videostore/videoclub_03_24_flickr.jpg +bakery/boulangerie_13_20_yahoo.jpg +clothingstore/boutique2.jpg +closet/NFD_30ClosetMaid.jpg +poolinside/pool_inside_25_20_altavista.jpg +restaurant/bistro_22_16_altavista.jpg +shoeshop/shoes_shop_48_08_altavista.jpg +mall/PublicP117C_L.jpg +restaurant_kitchen/restaurant_kitchen_google_0016.jpg +artstudio/painters_studio_03_18_altavista.jpg +kitchen/kitchen35.jpg +winecellar/bodega_07_14_yahoo.jpg +pantry/pantry_02_18_altavista.jpg +concert_hall/120703.jpg +kitchen/kitchen060.jpg +laboratorywet/wet_lab_13_03_altavista.jpg +livingroom/roomscan27.jpg +videostore/videotheque_02_05_flickr.jpg +waitingroom/rujin_femme.jpg +casino/casino_0042.jpg +elevator/elevator_google_0100.jpg +children_room/FW_97_2_0861_19_l.jpg +locker_room/locker_room_google_0098.jpg +restaurant/food_Bistro_450.jpg +garage/aug_outside04.jpg +kindergarden/2Preschool.jpg +tv_studio/52014180_110805_crw_3248_kteh_27_.jpg +bakery/bakery_06_11_altavista.jpg +videostore/blockbuster_32_13_altavista.jpg +locker_room/locker_room_google_0084.jpg +computerroom/CEJBA_sala_camputadoras.jpg +bathroom/room317.jpg +gameroom/P6170172.jpg +artstudio/art_painting_studio_01_15_altavista.jpg +greenhouse/conservatory_greenhouse_big.jpg +grocerystore/grocery_3.jpg +restaurant_kitchen/restaurant_kitchen_google_0001.jpg +concert_hall/W020070919331447037125.jpg +hospitalroom/hospital_room_38_10_altavista.jpg +livingroom/va_02_01_6306_03_l.jpg +classroom/30_AvH_112_classroom.jpg +prisoncell/robben_island.jpg +bedroom/indoor_0061.jpg +nursery/replace_orange_400x302.jpg +winecellar/bodega_78_08_flickr.jpg +dentaloffice/1796458857_21fc169bd0.jpg +inside_bus/inside_bus_100.jpg +mall/Shopping-mall.jpg +concert_hall/crbst_pict0027.jpg +tv_studio/2244288989_47ec1d796b_17_.jpg +grocerystore/kays_1.jpg +buffet/1351747750_8bbafedfd9.jpg +buffet/buffet57.jpg +cloister/san_millan_claustro.jpg +kitchen/kitchen036.jpg +mall/IMG_0111_Takashimayafoyer.jpg +winecellar/bodega_01_10_flickr.jpg +laboratorywet/laboratorio_quimica_14_01_altavista.jpg +kitchen/2335_1.jpg +bowling/bowling_0141.jpg +fastfood_restaurant/fast_food_restaurant_pef05022.jpg +poolinside/pool_inside_300_x_253_1_.jpg +prisoncell/jailcell234.jpg +studiomusic/MusicStudio.jpg +waitingroom/photo_hwhc_waiting.jpg +auditorium/amphi_1__81.jpg +dentaloffice/dentista_109_05_flickr.jpg +winecellar/cave_vin_25_17_altavista.jpg +bowling/bowling_0143.jpg +restaurant/coin_sympa.jpg +auditorium/la_grand_1__62.jpg +restaurant_kitchen/restaurant_kitchen_google_0018.jpg +inside_bus/inside_bus_041.jpg +garage/Basement_2_.jpg +locker_room/locker_room_google_0131.jpg +stairscase/hall21.jpg +casino/casino_0005.jpg +operating_room/surgery_room_08_03_altavista.jpg +classroom/web_lalemant.jpg +office/judges_office.jpg +bedroom/madison_room.jpg +artstudio/art_painting_studio_31_14_altavista.jpg +airport_inside/airport_inside_0155.jpg +poolinside/piscine_interieur_2.jpg +restaurant_kitchen/restaurant_kitchen_google_0097.jpg +subway/subway_0308.jpg +clothingstore/bestshopyet_full.jpg +children_room/AT_04_02_3000_55_l.jpg +locker_room/locker_room_google_0068.jpg +lobby/01_priv_ent.jpg +movietheater/sala_de_cine_21_05_altavista.jpg +grocerystore/Spirituosen_im_supermarkt_1.jpg +lobby/lobby223233.jpg +kitchen/kitchen99.jpg +laboratorywet/wetlab2.jpg +laundromat/PO_06_02_2000_70_l.jpg +stairscase/S32.jpg +livingroom/int122.jpg +florist/16944_7439_6.jpg +hairsalon/bild5_gross.jpg +inside_subway/inside_subway_0015.jpg +livingroom/indoor_0479.jpg +artstudio/artist_studio_14_12_altavista.jpg +cloister/Mosteiro_da_Batalha_Claustro_2.jpg +children_room/playroom_500_67_.jpg +prisoncell/cellDM1405_468x300.jpg +restaurant_kitchen/restaurant_kitchen_google_0041.jpg +tv_studio/tvstudio_120_.jpg +locker_room/locker_room_google_0175.jpg +bar/bar_0036.jpg +laundromat/100_2490_357122904_std.jpg +greenhouse/main.jpg +office/homeoff011.jpg +classroom/photo_classroom.jpg +greenhouse/greenhouse2gtr.jpg +shoeshop/zapateria_08_03_flickr.jpg +artstudio/artist_studio_43_07_altavista.jpg +mall/_69D9FF75_39DD_4F94_9FF4_6F01F5798173_.jpg +airport_inside/airport_inside_0175.jpg +corridor/p1010089_c.jpg +bedroom/indoor_0133.jpg +subway/subway_0263.jpg +artstudio/art_painting_studio_13_16_altavista.jpg +florist/c04652600522c7ad139144ca0e2b_3_4.jpg +gameroom/LV_00_01_5962_21_l.jpg +subway/subway_0318.jpg +movietheater/1190197963img_0239_1__3.jpg +nursery/dsc_0713_thumb_480xauto.jpg +corridor/IMG_3275.jpg +gameroom/gameroom.jpg +movietheater/theater_brady_450.jpg +laundromat/PB_99_4_7760_35A_l.jpg +museum/museo_134_21_flickr.jpg +bowling/bowling_0140.jpg +casino/casino_0478.jpg +hairsalon/Hair_Salon_Greensboro_NC_27406_235115.jpg +videostore/blockbuster_24_07_altavista.jpg +kindergarden/kindergarten_2.jpg +kitchen/indoor_0281.jpg +toystore/toys_store_21_19_altavista.jpg +garage/1.jpg +deli/deli_13_07_flickr.jpg +lobby/lobby33.jpg +bathroom/b3.jpg +florist/f_floristeria_04g.jpg +clothingstore/Purple_Tulip_Boutique_Photo.jpg +videostore/videoteca_06_03_flickr.jpg +clothingstore/shop01.jpg +jewelleryshop/store3_48_.jpg +mall/mallarizonamills01.jpg +nursery/baby_room_2.jpg +livingroom/room136.jpg +winecellar/bodega_152_04_flickr.jpg +casino/casino_0021.jpg +florist/florist_73_06_flickr.jpg +studiomusic/851_bandroom.jpg +laboratorywet/wet_lab_39_17_altavista.jpg +tv_studio/nice_casino_50_16_altavista.jpg +dining_room/dining9.jpg +meeting_room/int200.jpg +children_room/VA_02_05_7656_19A_l.jpg +videostore/videotheque_01_22_flickr.jpg +bakery/bakery34.jpg +dentaloffice/dental_chair.jpg +fastfood_restaurant/chick_fil_a.jpg +winecellar/cave_champagne_05_15_altavista.jpg +closet/url.jpg +gym/gym09.jpg +inside_bus/inside_bus_033.jpg +inside_subway/inside_subway_0240.jpg +office/d53.jpg +restaurant/restaurante_04_06_altavista.jpg +elevator/elevator_google_0040.jpg +kitchen/kitchen061.jpg +artstudio/artist_studio_43_03_altavista.jpg +bedroom/b30.jpg +gameroom/HO_06_02_2000_99_l.jpg +inside_subway/inside_subway_0250.jpg +meeting_room/conference1.jpg +subway/subway_0127.jpg +jewelleryshop/7777.jpg +bedroom/indoor_0186.jpg +tv_studio/peter_morse_teaching_unimelb_preview_55_.jpg +lobby/20076262_Collection_Lobby_02_w.jpg +casino/casino_0121.jpg +waitingroom/Concourse.jpg +operating_room/operating_room_34_11_altavista.jpg +cloister/claustro_interior.jpg +florist/florist_32_21_flickr.jpg +clothingstore/428460833_e89f97664b_b.jpg +laundromat/lavanderia_107_04_flickr.jpg +hospitalroom/LLIT_OBERT.jpg +cloister/claustro234.jpg +shoeshop/redwing.jpg +pantry/KitchenPantry.jpg +videostore/img_76381_renta.jpg +auditorium/inauguration_amphitheatre_1__151.jpg +trainstation/gare_129_07_flickr.jpg +hospitalroom/Birthing_Class7.jpg +fastfood_restaurant/539w.jpg +florist/cb72f48b619b1ff77bd11478d.jpg +garage/garage_workshop.jpg +pantry/section_pantry.jpg +jewelleryshop/299846537.jpg +waitingroom/waiting_room_49_19_altavista.jpg +studiomusic/RecordingStudio.jpg +winecellar/bodega_40_09_yahoo.jpg +auditorium/lar22_main_auditorium_a_copy_61.jpg +hairsalon/windor_hair_salon_189_380.jpg +classroom/IMG_2226.jpg +office/n457053.jpg +greenhouse/Invernadero.jpg +livingroom/l5.jpg +church_inside/St_Germain_des_Pr_s_autel.jpg +airport_inside/airport_inside_0232.jpg +corridor/n190054.jpg +bedroom/indoor_0567.jpg +waitingroom/CRL_Waitingarea.jpg +operating_room/surgery_room_25_11_altavista.jpg +dining_room/dining10.jpg +hospitalroom/hospital_room_36_11_altavista.jpg +studiomusic/solarstudio.jpg +hospitalroom/hospital_room_30_05_altavista.jpg +elevator/elevator_google_0053.jpg +nursery/baby_room_2_sized.jpg +shoeshop/t050dh02.jpg +warehouse/warehouse_0359.jpg +casino/casino_0177.jpg +operating_room/surgery_room_09_20_altavista.jpg +garage/10_GARAJE_3_JPG.jpg +artstudio/art_painting_studio_32_18_altavista.jpg +dining_room/easyst048.jpg +corridor/IMG_3081.jpg +laundromat/Ricks_laundromat_pictures_april_2007_139.jpg +florist/florist_21_16_flickr.jpg +jewelleryshop/3607308_scaled_416x312.jpg +tv_studio/tv_studio_05_12_altavista.jpg +buffet/ov_banquet_buffet2.jpg +meeting_room/conference_room.jpg +grocerystore/gs_image_Grocery_LEED_09_10.jpg +trainstation/gare_67_14_flickr.jpg +operating_room/surgery_room_09_12_altavista.jpg +lobby/sLobby18.jpg +bookstore/Libreria_44_19_altavista.jpg +livingroom/or_02_03_0212_36_l.jpg +videostore/videoclub_04_03_flickr.jpg +bakery/bakery_09_11_yahoo.jpg +warehouse/warehouse_0011.jpg +locker_room/locker_room_google_0232.jpg +shoeshop/75421584_9554abbb01.jpg +library/library_chess.jpg +museum/museum_44_15_altavista.jpg +dining_room/int130.jpg +waitingroom/wroom2.jpg +bar/bar_0086.jpg +hairsalon/peluqueria03.jpg +stairscase/N457075.jpg +dentaloffice/dentaire_03_19_altavista.jpg +trainstation/gare_51_05_flickr.jpg +tv_studio/estudio_rbs_tv_pelotas_45_.jpg +shoeshop/172402.jpg +deli/deli_129_10_flickr.jpg +prisoncell/prison_cell_36_02_altavista.jpg +prisoncell/P2170478.jpg +artstudio/painters_studio_08_03_altavista.jpg +bathroom/indoor_0566.jpg +hospitalroom/hospital_room_1.jpg +bedroom/b22.jpg +lobby/00320003.jpg +stairscase/D05b.jpg +bowling/bowling_0033.jpg +locker_room/locker_room_google_0218.jpg +office/office4.jpg +waitingroom/PDR_0026.jpg +restaurant_kitchen/restaurant_kitchen_google_0047.jpg +toystore/5.jpg +restaurant_kitchen/restaurant_kitchen_google_0056.jpg +buffet/23142_1177445820.jpg +kindergarden/playroom.jpg +mall/mall37.jpg +movietheater/p1010962_1__25.jpg +stairscase/OR_99_5_9379_27_l.jpg +warehouse/warehouse_0231.jpg +mall/West_End_Mall_DSC_0550_m.jpg +bookstore/3e7e40981ddd4cdb055c7ffe4c2af5d8.jpg +toystore/south_haven_michigan_toy_educational_store4.jpg +bakery/panetteria_01_13_altavista.jpg +church_inside/metropolitana_75_05_flickr.jpg +gym/gym13.jpg +studiomusic/datos_adjuntos.jpg +artstudio/painters_studio_19_05_altavista.jpg +office/office12.jpg +bakery/panaderia_49_18_yahoo.jpg +restaurant/bistro_01_06_altavista.jpg +waitingroom/PHOTO22.jpg +museum/museum_07_15_altavista.jpg +subway/subway_0260.jpg +closet/master_closet.jpg +greenhouse/088_invernadero.jpg +restaurant/bistro_31_02_altavista.jpg +garage/home_garage_cabinets.jpg +studiomusic/mixingdesk.jpg +laboratorywet/laboratorio_quimica_13_12_altavista.jpg +elevator/elevator_google_0001.jpg +meeting_room/n457008.jpg +movietheater/movietheater_google_0011.jpg +warehouse/warehouse_0498.jpg +deli/deli_04_14_altavista.jpg +winecellar/wine_cellar_45_01_altavista.jpg +subway/subway_0504.jpg +airport_inside/airport_inside_0497.jpg +deli/deli_31_04_altavista.jpg +lobby/Marshall_McLuhan_Salon_1.jpg +computerroom/sala_informatica.jpg +prisoncell/url.jpg +artstudio/art_painting_studio_13_20_altavista.jpg +office/eagle_office.jpg +poolinside/pool_inside_04_11_altavista.jpg +cloister/Cloister92.jpg +florist/floreria_09_04_flickr.jpg +laboratorywet/wet-lab.jpg +museum/museo_03_24_flickr.jpg +airport_inside/airport_inside_0043.jpg +deli/deli_148_16_flickr.jpg +grocerystore/supermarche_1.jpg +kitchen/kitchen045.jpg +subway/subway_0185.jpg +deli/deli_28_22_flickr.jpg +laboratorywet/wet_lab_11_02_altavista.jpg +locker_room/locker_room_google_0213.jpg +restaurant/restaurant_32_19_altavista.jpg +classroom/groupe_scolaire_2.jpg +videostore/gn704.jpg +operating_room/surgery_room_09_01_altavista.jpg +greenhouse/horti_estufa.jpg +kindergarden/Preschool_Kindergarten_Classroom_full.jpg +garage/3_5_08_garage.jpg +videostore/club_video_26_08_altavista.jpg +prisoncell/jail_cell321.jpg +corridor/img_9683_c.jpg +greenhouse/jardin_celeste_nouv.jpg +jewelleryshop/12907.jpg +artstudio/art_studio_01_01_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0019.jpg +kitchen/int42.jpg +mall/mall38.jpg +laundromat/Laundromat_013.jpg +winecellar/bodega_153_13_flickr.jpg +clothingstore/fashionfix_drift21.jpg +classroom/IM000820.jpg +greenhouse/greenhouseoi1.jpg +clothingstore/fall_2006_B_146.jpg +clothingstore/mob989_1158475595.jpg +mall/latham_circle_mall_16.jpg +bedroom/b13.jpg +kindergarden/classroom223.jpg +dentaloffice/dental_office_40_05_altavista.jpg +inside_bus/inside_bus_009.jpg +bedroom/cimg8169.jpg +bookstore/Libreria_02_01_altavista.jpg +bakery/panaderia_15_06_yahoo.jpg +bowling/bowling_0171.jpg +computerroom/board05e.jpg +museum/museo_10_06_flickr.jpg +elevator/elevator_google_0019.jpg +closet/082806_the_stash_closet.jpg +nursery/lpbedding_hero.jpg +winecellar/wine_storage_05_20_altavista.jpg +computerroom/SALA_DE_INFORMaTICA.jpg +locker_room/locker_room_google_0163.jpg +winecellar/wine_cellar_37_13_altavista.jpg +tv_studio/16580_2_9_.jpg +poolinside/Inside-Pool.jpg +airport_inside/airport_inside_0549.jpg +florist/florist_01_18_altavista.jpg +subway/subway_0098.jpg +fastfood_restaurant/arbys4.jpg +classroom/118463509_99bde44e96.jpg +garage/garaje4567.jpg +livingroom/room502.jpg +movietheater/movietheater_google_0021.jpg +kitchen/k11.jpg +dentaloffice/img_op_room_bg.jpg +fastfood_restaurant/1213796_gross_burger_king_0.jpg +trainstation/gare_04_18_flickr.jpg +gym/pacific_newport_gym.jpg +airport_inside/airport_inside_0194.jpg +bar/bar_0080.jpg +cloister/IMG_0433.jpg +livingroom/indoor_0597.jpg +closet/section_closet.jpg +kitchen/cdmc1128.jpg +garage/Garage_Grid_10.jpg +buffet/137902351_6f2025ac9d.jpg +artstudio/art_painting_studio_47_02_altavista.jpg +toystore/toys_store_34_07_altavista.jpg +trainstation/gare_97_23_flickr.jpg +warehouse/warehouse_0151.jpg +shoeshop/zapateria_20_22_flickr.jpg +videostore/videoclub_09_17_flickr.jpg +concert_hall/konzertsaal.jpg +gameroom/sala_de_juegos_03_20_altavista.jpg +bedroom/easyst031.jpg +buffet/url_1.jpg +dentaloffice/dental_office_08_12_altavista.jpg +movietheater/movie_THeater.jpg +pantry/Warm_Cognac_Pantry.jpg +museum/museum_27_08_altavista.jpg +trainstation/estacion_de_ferrocarriles_35_20_altavista.jpg +airport_inside/airport_inside_0099.jpg +bathroom/room319.jpg +nursery/bali_nursery.jpg +buffet/358396421_aa2d3f6f03.jpg +bookstore/Bookstore111.jpg +restaurant/salle63.jpg +prisoncell/Jail.jpg +meeting_room/conference7.jpg +gameroom/Gameroom3.jpg +trainstation/gare_144_17_flickr.jpg +prisoncell/jackson_jail_1.jpg +hospitalroom/int173.jpg +bakery/boulangerie_17_17_yahoo.jpg +toystore/n8.jpg +locker_room/locker_room_google_0113.jpg +movietheater/sala-31.jpg +laundromat/laund_doc_010.jpg +dentaloffice/dental_office_13_13_altavista.jpg +fastfood_restaurant/url.jpg +poolinside/catalogue_piscine_interieur.jpg +dentaloffice/dentista_oficina_01_01_flickr.jpg +livingroom/living49.jpg +inside_subway/metropolitana_128_13_flickr.jpg +restaurant_kitchen/restaurant_kitchen_google_0108.jpg +concert_hall/luzerne.jpg +auditorium/06salle_1__2.jpg +prisoncell/323011538894332.jpg +gym/983000854_2bfd6a9ccf.jpg +airport_inside/airport_inside_0211.jpg +gameroom/gameroom2_600x448.jpg +casino/casino_0285.jpg +fastfood_restaurant/subway_gif.jpg +laundromat/lavanderia_40_18_flickr.jpg +laundromat/wasmachine.jpg +deli/deli_84_07_flickr.jpg +studiomusic/int597.jpg +shoeshop/zapateria_15_19_flickr.jpg +clothingstore/main_store_pic_01.jpg +mall/2038935077_84f209ff2b.jpg +bedroom/room405.jpg +kitchen/cdmc1144.jpg +church_inside/metropolitana_71_22_flickr.jpg +hospitalroom/hospital_room_02_18_altavista.jpg +inside_subway/inside_subway_0163.jpg +poolinside/Pool_Inside_Out.jpg +bathroom/n190042.jpg +livingroom/l8.jpg +children_room/img_3808_24_.jpg +florist/floreria_05_01_flickr.jpg +auditorium/auditorium68_115.jpg +dining_room/cdmc1195.jpg +locker_room/locker_room_google_0210.jpg +fastfood_restaurant/restaurant_panda.jpg +waitingroom/celibre_waiting_room.jpg +hairsalon/17630_7927_qsoms.jpg +inside_subway/inside_subway_0397.jpg +prisoncell/153430565_b7c67fd743.jpg +restaurant_kitchen/restaurant_kitchen_google_0044.jpg +gameroom/int92.jpg +restaurant_kitchen/restaurant_kitchen_google_0005.jpg +classroom/file_279474_23340.jpg +fastfood_restaurant/universitycenter056.jpg +gym/gimnasio_18_01_altavista.jpg +church_inside/metropolitana_14_19_flickr.jpg +tv_studio/tv_studio_01_03_altavista.jpg +bakery/bakery_02_03_altavista.jpg +children_room/VA_05_03_8000_49_l.jpg +jewelleryshop/boutique66.jpg +corridor/corridora8.jpg +meeting_room/room291.jpg +restaurant/gaststaette15.jpg +inside_subway/inside_subway_0273.jpg +meeting_room/conf19.jpg +children_room/OR_99_1_4913_31_l.jpg +casino/casino_0017.jpg +tv_studio/tv_studio_48_15_altavista.jpg +bowling/bowling_0201.jpg +airport_inside/airport_inside_0302.jpg +buffet/296063760_c41c08fab3.jpg +office/homeoff015.jpg +artstudio/painters_studio_13_13_altavista.jpg +buffet/buffet001.jpg +tv_studio/37048_c738e97b_cefe_433b_8ba1_37b1e54b5396_21_.jpg +buffet/413438464_9118ab17b8.jpg +children_room/playroom7_65_.jpg +mall/deira_city_centre_dubai_08.jpg +trainstation/gare_139_21_flickr.jpg +inside_bus/inside_bus_074.jpg +laundromat/laundry_room1.jpg +deli/deli_138_13_flickr.jpg +studiomusic/Pegnotti_estudio_River.jpg +bakery/bakery_17_17_yahoo.jpg +dining_room/room411.jpg +mall/34970_1_0012.jpg +florist/florist_23_20_flickr.jpg +poolinside/47220_pool_inside8.jpg +waitingroom/sLobby23.jpg +movietheater/salle1_1__66.jpg +museum/museo_67_01_flickr.jpg +fastfood_restaurant/Pizza_hut_express.jpg +grocerystore/grocery2.jpg +stairscase/dining044.jpg +classroom/int115.jpg +meeting_room/conf18.jpg +bookstore/bookstore_15_05_altavista.jpg +computerroom/computer_room_1.jpg +garage/garage_pics_003_1_.jpg +hairsalon/348_img159643_365652.jpg +bedroom/indoor_0330.jpg +nursery/nursery10.jpg +operating_room/operating_room_17_04_altavista.jpg +bowling/bowling_0015.jpg +casino/casino_0492.jpg +garage/hpgarageinside.jpg +subway/subway_0317.jpg +tv_studio/2004092000530101_12_.jpg +winecellar/wine_cellar_27_11_altavista.jpg +grocerystore/grocery23.jpg +cloister/Cloister456.jpg +livingroom/va_02_03_7115_16_l.jpg +warehouse/warehouse_0136.jpg +artstudio/art_painting_studio_25_15_altavista.jpg +gym/gimnasio_60_11_flickr.jpg +prisoncell/prison_cell_16_16_altavista.jpg +subway/subway_0277.jpg +subway/subway_0503.jpg +bar/bar_0429.jpg +livingroom/l7.jpg +movietheater/salle_cinema_metz_1__64.jpg +subway/subway_0273.jpg +laboratorywet/senior_wet_lab_1.jpg +greenhouse/1412_mb_file_0a8c5.jpg +lobby/sLobby21.jpg +warehouse/warehouse_0416.jpg +concert_hall/dekelboum2.jpg +studiomusic/Jw1qBartists1166828911.jpg +auditorium/amphi1_1__79.jpg +laboratorywet/wet_lab_26_05_altavista.jpg +locker_room/locker_room_google_0130.jpg +garage/basement_garage.jpg +restaurant/restaurant_16_05_altavista.jpg +nursery/c0020759_baby_room1_large.jpg +grocerystore/dogfooddisplaypiggb7xe.jpg +videostore/blockbuster_49_08_flickr.jpg +kitchen/kitchen137.jpg +auditorium/4875_amphitheatre_5_1__11.jpg +videostore/video_store_43_09_altavista.jpg +dining_room/dining46.jpg +garage/salmon_garage_after_gif.jpg +locker_room/locker_room_google_0011.jpg +classroom/Classroom9.jpg +kindergarden/SA_Pre_K_PS_Classroom_pictures_002.jpg +lobby/sLobby12.jpg +bar/bar_0031.jpg +hairsalon/SalonCoiffureMontelimar4.jpg +livingroom/room1.jpg +inside_subway/inside_subway_0257.jpg +shoeshop/zapateria_09_11_flickr.jpg +studiomusic/int807.jpg +bakery/best_bakery_02_06_altavista.jpg +hairsalon/hair_salon_hl.jpg +laundromat/alex_winch8.jpg +shoeshop/zapateria_22_08_flickr.jpg +livingroom/pb_99_1_0532_33_l.jpg +operating_room/operating_room_05_11_altavista.jpg +bar/bar_0407.jpg +subway/subway_0045.jpg +lobby/lobby3456.jpg +church_inside/1348333936_c97997d450.jpg +closet/closet2i.jpg +hospitalroom/00040220.jpg +dining_room/dining015.jpg +dining_room/d1.jpg +stairscase/maison_moissac_82_2.jpg +subway/subway_0261.jpg +toystore/speelgoed_66_02_flickr.jpg +tv_studio/burkebushbillm1967_30_.jpg +grocerystore/400_F_2072702_0czrOPPRxph5STUVfX3QxpCrEk80sG.jpg +gameroom/HO_00_01_4523_26A_l.jpg +waitingroom/waiting_room_03_13_altavista.jpg +winecellar/wine_cellar_main.jpg +buffet/31248717_88518f1e6d.jpg +artstudio/painters_studio_41_20_altavista.jpg +greenhouse/greenhouse2_1.jpg +locker_room/locker_room_google_0197.jpg +stairscase/LV_98_3_8762_18_l.jpg +airport_inside/airport_inside_0092.jpg +studiomusic/djban_estudio_musical.jpg +children_room/dscn0208_15_.jpg +church_inside/metropolitana_31_10_flickr.jpg +inside_bus/inside_bus_072.jpg +winecellar/wine_cellar_35_06_altavista.jpg +gameroom/Salle_expositions_jeux_d_echecs_02.jpg +waitingroom/pictureb_waitingroom.jpg +videostore/blockbuster_23_20_flickr.jpg +livingroom/cdmc1298.jpg +prisoncell/oldJail_Run.jpg +trainstation/estacion_de_ferrocarriles_49_09_altavista.jpg +corridor/n457064.jpg +toystore/jugueteria_16_08_flickr.jpg +airport_inside/airport_inside_0239.jpg +lobby/Art_Steinman_800_567.jpg +trainstation/gare_140_03_flickr.jpg +cloister/2611191511_4deb4bcf31.jpg +locker_room/locker_room_google_0036.jpg +prisoncell/justice_7.jpg +laboratorywet/wet_lab_35_20_altavista.jpg +pantry/pantry_57_22_flickr.jpg +garage/lot_16_garage.jpg +poolinside/LOU_HURS_pooli_1.jpg +warehouse/warehouse_0225.jpg +florist/florist_shop.jpg +lobby/halld_entree_500x354.jpg +trainstation/gare_99_08_flickr.jpg +meeting_room/n457038.jpg +casino/casino_0316.jpg +dentaloffice/dentista_93_04_flickr.jpg +nursery/nursery1a_1.jpg +shoeshop/panel_zapatos.jpg +dentaloffice/dental_office_47_13_altavista.jpg +dining_room/d6.jpg +restaurant/restaurant_08_17_altavista.jpg +fastfood_restaurant/Arbys_2.jpg +bakery/boulangerie_35_01_altavista.jpg +dentaloffice/aboutus_image1.jpg +laboratorywet/laboratorio_quimica_15_03_altavista.jpg +museum/galleria.jpg +locker_room/locker_room_google_0072.jpg +inside_subway/inside_subway_0220.jpg +cloister/cloister_cc_miladus.jpg +clothingstore/boutique9.jpg +kitchen/kitchen17.jpg +restaurant/restaurant_23_05_altavista.jpg +gym/GYM_C.jpg +locker_room/locker_room_google_0093.jpg +operating_room/operating_room_39_02_altavista.jpg +livingroom/living74.jpg +bedroom/b27.jpg +corridor/p1010090_c.jpg +inside_subway/inside_subway_0418.jpg +jewelleryshop/jewelry_dealer_business_38_.jpg +corridor/corridora2.jpg +church_inside/kirche_stettfeld_i.jpg +clothingstore/CP67794.jpg +livingroom/l9.jpg +stairscase/room228.jpg +trainstation/train_station_02_20_altavista.jpg +gym/gym_37_06_altavista.jpg +airport_inside/airport_inside_0224.jpg +toystore/jugueteria_25_08_yahoo.jpg +pantry/pantry_12_20_flickr.jpg +prisoncell/Penh2.jpg +fastfood_restaurant/3.jpg +pantry/pantry_05_24_flickr.jpg +bookstore/bookstore_21_14_flickr.jpg +elevator/elevator_google_0017.jpg +airport_inside/airport_inside_0288.jpg +bar/bar_0206.jpg +computerroom/Room_002s.jpg +warehouse/warehouse_0066.jpg +operating_room/operating_room_07_14_altavista.jpg +library/librairie.jpg +fastfood_restaurant/hut1.jpg +casino/casino_0040.jpg +kindergarden/housekeeping_center_18202041_std.jpg +bakery/panaderia_07_02_yahoo.jpg +kindergarden/51122_1124224733_6.jpg +toystore/jugueteria_19_18_flickr.jpg +artstudio/art_painting_studio_21_17_altavista.jpg +videostore/videoclub_10_12_flickr.jpg +clothingstore/0020.jpg +nursery/cmag0308_babyrooms02.jpg +subway/subway_0498.jpg +bathroom/indoor_0258.jpg +tv_studio/estudio_de_television_83_.jpg +inside_subway/inside_subway_0177.jpg +greenhouse/2_wallington_greenhouse_470x353.jpg +prisoncell/cell4.jpg +restaurant/restaurante_24_15_altavista.jpg +gym/southglade_gym_2.jpg +jewelleryshop/Interior1.jpg +kitchen/kitchen5.jpg +operating_room/operating_room_26_19_altavista.jpg +artstudio/painters_studio_03_12_altavista.jpg +hairsalon/DSCN3262_modifie_1.jpg +airport_inside/airport_inside_0570.jpg +kitchen/kitchen031.jpg +stairscase/room476.jpg +cloister/BAC09297.jpg +jewelleryshop/joyeria_10_20_altavista.jpg +videostore/blockbuster_46_07_flickr.jpg +lobby/9dVestibule_2_After_.jpg +artstudio/artistic_studio_01_08_altavista.jpg +bowling/bowling_0091.jpg +fastfood_restaurant/hut4.jpg +airport_inside/airport_inside_0013.jpg +shoeshop/za4.jpg +museum/museo_149_10_flickr.jpg +hairsalon/BIG_20080418112453.jpg +kindergarden/PreschoolKids.jpg +florist/florist_02_18_flickr.jpg +cloister/east_cloister320.jpg +florist/floreria_09_24_flickr.jpg +trainstation/train_station_24_08_altavista.jpg +hairsalon/salon3.jpg +studiomusic/Music_Studio.jpg +hairsalon/296614426_3b58446e42.jpg +inside_bus/inside_bus_066.jpg +bookstore/bookstore_49_17_altavista.jpg +cloister/build1385.jpg +auditorium/auditorium3_1__36.jpg +bowling/bowling_0050.jpg +closet/Maple_Tower_Doors.jpg +grocerystore/610x.jpg +inside_subway/inside_subway_0088.jpg +artstudio/artistic_studio_05_19_altavista.jpg +casino/casino_0047.jpg +artstudio/art_painting_studio_05_06_altavista.jpg +hairsalon/p01_10_07_1833.jpg +kitchen/kitchen177.jpg +mall/Galleria_003.jpg +cloister/Moissac_cloister.jpg +locker_room/locker_room_google_0189.jpg +trainstation/gare_138_18_flickr.jpg +auditorium/auditorium8_1__119.jpg +concert_hall/news_palms_web505.jpg +church_inside/betischurch_interior.jpg +gameroom/119gameroombig.jpg +museum/museo_134_07_flickr.jpg +computerroom/garantia.jpg +hairsalon/suburbia_hair_3.jpg +laboratorywet/laboratorio_quimica_14_03_altavista.jpg +church_inside/kirche_sao_lourenco_almancil.jpg +hospitalroom/CIMG0952.jpg +gym/gimnasio_24_03_altavista.jpg +toystore/toys_store_23_18_altavista.jpg +bar/bar_0130.jpg +bar/bar_0001.jpg +corridor/quiet_corridor_c.jpg +waitingroom/sLobby02.jpg +bookstore/Librairie_45_19_altavista.jpg +kindergarden/IMG_2327_t600.jpg +airport_inside/airport_inside_0183.jpg +stairscase/0307280855117les8.jpg +trainstation/gare_15_04_flickr.jpg +hospitalroom/Ben_001.jpg +kindergarden/PRESCHOOL_PLAYMAT2.jpg +airport_inside/airport_inside_0190.jpg +livingroom/easyst022.jpg +kitchen/iclock.jpg +lobby/uploads_images_photos_images_fullsize_hotellobby.jpg +shoeshop/moder.jpg +shoeshop/Browns2.jpg +videostore/2vhs.jpg +concert_hall/Schermerhorn.jpg +elevator/elevator_google_0061.jpg +deli/deli_118_06_flickr.jpg +bakery/bakery_32_04_yahoo.jpg +shoeshop/zapateria_25_17_flickr.jpg +kitchen/cdmc1175.jpg +movietheater/movietheater_google_0026.jpg +stairscase/N67.jpg +winecellar/bodega_vino_01_17_altavista.jpg +cloister/salisbury_cloister_350.jpg +poolinside/pool_inside_06_10_altavista.jpg +studiomusic/wideshot_main_studio.jpg +nursery/seeing_stripes_beautiful_brown_and_blue_baby_boys_nursery_10422.jpg +fastfood_restaurant/OrangeIn.jpg +stairscase/stairs04.jpg +inside_subway/inside_subway_0029.jpg +florist/fleuriste2.jpg +waitingroom/Aug29_2007.jpg +bathroom/el_cuarto_de_bano.jpg +classroom/IPODIUM.jpg +museum/museo_83_02_flickr.jpg +stairscase/int697.jpg +bakery/bakery7.jpg +auditorium/booker_20twashingtonhs_auditorium450b_1__48.jpg +clothingstore/ins32.jpg +kitchen/k8.jpg +tv_studio/tv_studio_2_112_.jpg +greenhouse/20070418klpcnaecl_364_Ies_SCO.jpg +grocerystore/9d37cca1_088e_4812_a319_9f8d3fcf37a1.jpg +church_inside/oberkirche01.jpg +clothingstore/Kleidung1.jpg +shoeshop/zapateria_21_03_flickr.jpg +toystore/Spielzeug_56_09_flickr.jpg +movietheater/3511628962884727_0.jpg +children_room/AT_01_3B_5486_10_l.jpg +classroom/classroom_710755.jpg +subway/subway_0358.jpg +kitchen/kitchen125.jpg +cloister/afm_cloister.jpg +auditorium/11472676233attendees_in_the_main_auditorium_1_1__3.jpg +dining_room/dining032.jpg +florist/Flower_shop_Kuala_Lumpur.jpg +library/28_06_06_Bibliotheque_Municipale_19_2.jpg +operating_room/operating_room_20_04_altavista.jpg +poolinside/piscine_interieur_3.jpg +hospitalroom/DSC01127.jpg +restaurant/restaurant_19_16_altavista.jpg +operating_room/surgery_room_16_17_altavista.jpg +bar/bar_0151.jpg +computerroom/electronica.jpg +bookstore/Librairie_22_12_altavista.jpg +florist/florist_40_04_altavista.jpg +prisoncell/fatherscell_01.jpg +restaurant_kitchen/restaurant_kitchen_google_0078.jpg +kindergarden/PreschoolClassroom1.jpg +restaurant_kitchen/restaurant_kitchen_google_0061.jpg +dentaloffice/1626665740_37aaacaff5.jpg +dining_room/d9.jpg +jewelleryshop/joyeria10.jpg +videostore/video_store_19_05_altavista.jpg +bookstore/spellbinding_bookstore.jpg +restaurant_kitchen/restaurant_kitchen_google_0067.jpg +gym/gimnasio_10_08_altavista.jpg +hairsalon/salon6.jpg +movietheater/ccs_3_1__19.jpg +bathroom/b10.jpg +gameroom/VA_04_02_6000_24_l.jpg +church_inside/kirche_uebergang.jpg +prisoncell/15208484cellblock.jpg +casino/casino_0010.jpg +mall/4Larcomar_Shopping_Center.jpg +bathroom/indoor_0461.jpg +gym/gym001.jpg +livingroom/living53.jpg +airport_inside/airport_inside_0261.jpg +closet/Cognac_RI_Cropped_Shot.jpg +meeting_room/n457020.jpg +meeting_room/conf15.jpg +pantry/pantry_12_11_flickr.jpg +videostore/video_store_04_15_altavista.jpg +closet/thumb_l_31.jpg +laundromat/lavanderia_47_23_flickr.jpg +library/library03.jpg +hospitalroom/hospital_room_37_19_altavista.jpg +laundromat/Catskill_Hall_Laundry_Room.jpg +bar/bar_0038.jpg +trainstation/gare_95_19_flickr.jpg +bar/bar_0570.jpg +casino/casino_0424.jpg +livingroom/int114.jpg +dentaloffice/dentista_65_14_flickr.jpg +waitingroom/MeetingPlace.jpg +deli/deli_18_02_yahoo.jpg +trainstation/gare_107_03_flickr.jpg +kitchen/cdmc1126.jpg +dining_room/dining039.jpg +restaurant_kitchen/restaurant_kitchen_google_0079.jpg +inside_subway/inside_subway_0428.jpg +church_inside/metropolitana_97_12_flickr.jpg +artstudio/painters_studio_31_18_altavista.jpg +artstudio/art_painting_studio_44_08_altavista.jpg +dining_room/va_02_04_7112_05_l.jpg +restaurant/restaurant_01_09_altavista.jpg +stairscase/N190008.jpg +tv_studio/dscn2300_copia_40_.jpg +fastfood_restaurant/Taco_Bell_9.jpg +greenhouse/spr2007_greenhouse_filling_up.jpg +hospitalroom/IMG_6996.jpg +winecellar/bodega_vino_11_12_altavista.jpg +inside_subway/inside_subway_0202.jpg +grocerystore/800px_Obst_supermarkt.jpg +bedroom/homeoff005.jpg +computerroom/Mvc_001f.jpg +gameroom/LV_02_04_10915_17_l.jpg +laundromat/lavanderia_62_16_flickr.jpg +studiomusic/BB_Studio_1_Mar2005_800.jpg +nursery/1662_z_1.jpg +winecellar/wine_storage_42_02_altavista.jpg +greenhouse/greenhouse276.jpg +office/despacho2.jpg +laboratorywet/wetlab.jpg +stairscase/OR_97_1_5167_13_l.jpg +bookstore/ins15.jpg +lobby/sLobby28.jpg +stairscase/va_02_04_7114_21a_l.jpg +bookstore/shakespearebookshop.jpg +classroom/classroom83.jpg +bowling/bowling_0084.jpg +kindergarden/lehuaroom.jpg +nursery/101345g_1.jpg +bedroom/b19.jpg +bookstore/Bookstore1_2.jpg +gym/Gym05.jpg +hairsalon/large_img_nj.jpg +tv_studio/tv_studio_18_01_altavista.jpg +bowling/bowling_0070.jpg +stairscase/D05a.jpg +poolinside/piscina_cubierta_03_04_altavista.jpg +fastfood_restaurant/barinfireworks.jpg +operating_room/surgery_room_28_08_altavista.jpg +dining_room/dining03.jpg +bowling/bowling_0043.jpg +restaurant_kitchen/restaurant_kitchen_google_0058.jpg +meeting_room/conf06.jpg +auditorium/star_20auditorium_202_20k_1__71.jpg +warehouse/warehouse_0211.jpg +cloister/Index_claustro.jpg +restaurant/restaurant_05_09_altavista.jpg +lobby/Fairmont_Lobby.jpg +inside_subway/inside_subway_0373.jpg +auditorium/auditorium_seating_1__33.jpg +cloister/claustro_pano_w.jpg +museum/museum_13_08_flickr.jpg +tv_studio/picfornewsletteraug82003sftechtvset_56_.jpg +children_room/western_playroom_71_.jpg +lobby/lobby9909.jpg +museum/museo_162_24_flickr.jpg +kindergarden/Toddler_room.jpg +closet/closet26.jpg +hospitalroom/int211.jpg +library/library02.jpg +prisoncell/jail_cell32.jpg +museum/museo_90_19_flickr.jpg +warehouse/warehouse_0031.jpg +corridor/corbusier_1_leg.jpg +airport_inside/airport_inside_0362.jpg +grocerystore/grocery.jpg +livingroom/in108.jpg +waitingroom/Salle_attente_clinilab_4.jpg +gameroom/P6170163.jpg +laundromat/DSC01042.jpg +nursery/ss_100446680.jpg +videostore/video_store_2_1.jpg +studiomusic/studio_2.jpg +fastfood_restaurant/snack.jpg +jewelleryshop/DSC01829.jpg +office/office2.jpg +stairscase/lobby30.jpg +livingroom/l3.jpg +museum/museum_38_17_altavista.jpg +grocerystore/shop04.jpg +laundromat/1209023191.jpg +greenhouse/greenhousetr_2.jpg +concert_hall/MAMusicBuildingView1_Small_.jpg +jewelleryshop/95_14.jpg +poolinside/swimming_pool_inside.jpg +gameroom/sallejeux78.jpg +corridor/corridor1.jpg +airport_inside/airport_inside_0182.jpg +office/ins9.jpg +deli/deli_14_16_yahoo.jpg +fastfood_restaurant/PH_ROCHESTER_INT3.jpg +shoeshop/zapateria_24_23_flickr.jpg +garage/mso1011_LadderBike_bef1_w609.jpg +bedroom/strongcpl_t.jpg +office/despacho1_400.jpg +corridor/hall_c.jpg +corridor/south_hallway_from_front_c.jpg +jewelleryshop/joyeria_157_15_flickr.jpg +kitchen/kitchen189.jpg +trainstation/gare_144_03_flickr.jpg +concert_hall/phbalcony.jpg +florist/florist_26_02_flickr.jpg +hairsalon/coiffure.jpg +restaurant/restaurant_43_20_altavista.jpg +warehouse/warehouse_0181.jpg +office/o1.jpg +casino/casino_0015.jpg +garage/garaje_norte_este.jpg +elevator/elevator_google_0082.jpg +florist/florist_41_17_altavista.jpg +shoeshop/zapatos_tematica.jpg +inside_subway/inside_subway_0143.jpg +poolinside/phil3.jpg +mall/70_12_03_01.jpg +kindergarden/pre3.jpg +artstudio/artist_work_place_24_16_altavista.jpg +cloister/cloisters.jpg +greenhouse/greenhouse2ln1.jpg +warehouse/warehouse_0166.jpg +bakery/bakery_21_08_yahoo.jpg +concert_hall/nemzeti_hangversenyterem.jpg +stairscase/AT_01_6B_5490_20_l.jpg +nursery/2190204970_7b449af783_o.jpg +restaurant/restaurante_35_12_altavista.jpg +stairscase/273_2_zoom_1_aminta_grand_hotel_sorrento_lounge.jpg +bookstore/Librairie_27_01_altavista.jpg +florist/florist_38_21_flickr.jpg +kitchen/int365.jpg +grocerystore/supermarche3_1.jpg +bathroom/bath17.jpg +artstudio/art_painting_studio_23_01_altavista.jpg +library/Bibliothek_kalocsa.jpg +airport_inside/airport_inside_0106.jpg +bathroom/room299.jpg +corridor/n457047.jpg +mall/West_End_Mall_2_DSC01799_m.jpg +bar/bar_0016.jpg +waitingroom/Greatclips_wait.jpg +children_room/portland_114_36_.jpg +bakery/bakery_04_03_altavista.jpg +trainstation/train_station_14_13_altavista.jpg +movietheater/auditorium_resized_18.jpg +bakery/boulangerie_01_06_yahoo.jpg +kitchen/indoor_0263.jpg +cloister/eandl.jpg +clothingstore/p0011.jpg +shoeshop/2008050303001187_375.jpg +hairsalon/friseur1.jpg +florist/florist_39_19_altavista.jpg +closet/Closet_After_1_.jpg +kindergarden/classroom.jpg +gameroom/6.jpg +concert_hall/vhstage.jpg +livingroom/indoor_0073.jpg +winecellar/cave_vin_41_15_altavista.jpg +operating_room/operating_room_26_01_altavista.jpg +office/office18.jpg +elevator/elevator_google_0089.jpg +hospitalroom/DSC_0097.jpg +stairscase/escalier377.jpg +church_inside/QuitoChurch.jpg +gym/gimnasio_137_08_flickr.jpg +kitchen/int347.jpg +laundromat/lavanderia_43_13_flickr.jpg +bedroom/indoor_0110.jpg +bar/bar_0235.jpg +prisoncell/Police_1.jpg +stairscase/spk_murnau_treppe1_big.jpg +hospitalroom/My_hospital_Room.jpg +inside_bus/inside_bus_004.jpg +grocerystore/big_Grocery_Store.jpg +kitchen/kitchen143.jpg +elevator/elevator_google_0066.jpg +greenhouse/serreyu.jpg +waitingroom/7.jpg +bookstore/Libreria_48_03_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0014.jpg +dentaloffice/Dental.jpg +children_room/AT_01_2B_5493_35_l.jpg +greenhouse/greenhouse_1.jpg +closet/closet_1.jpg +laboratorywet/wet_lab_03_15_altavista.jpg +bowling/bowling_0178.jpg +auditorium/auditorium39_112.jpg +meeting_room/c18.jpg +waitingroom/deco5_png.jpg +bar/bar_0054.jpg +office/home_office_after.jpg +subway/underground_56_07_flickr.jpg +greenhouse/serre_printemps.jpg +clothingstore/946590115_035579bce7_b.jpg +bar/bar_0061.jpg +bookstore/index_librairie.jpg +prisoncell/scrnewjailcell.jpg +toystore/Spielzeug_41_05_flickr.jpg +church_inside/kirche76.jpg +grocerystore/grocery74.jpg +warehouse/warehouse_0131.jpg +gym/gym_42_12_altavista.jpg +clothingstore/common.jpg +concert_hall/UMass_EX_3.jpg +waitingroom/waiting_room_31_12_altavista.jpg +hospitalroom/DSC_0454.jpg +concert_hall/cscseats.jpg +gym/gimnasio_38_16_flickr.jpg +winecellar/cave_champagne_09_20_altavista.jpg +corridor/2326_2.jpg +toystore/jugueteria_18_12_flickr.jpg +artstudio/art_painting_studio_32_17_altavista.jpg +laundromat/lavanderia_101_20_flickr.jpg +library/library5.jpg +museum/museo_14_11_altavista.jpg +warehouse/warehouse_0050.jpg +jewelleryshop/joyeria_66_11_flickr.jpg +elevator/elevator_google_0058.jpg +artstudio/painters_studio_18_10_altavista.jpg +airport_inside/airport_inside_0303.jpg +corridor/p1010067_c.jpg +auditorium/baird_1__141.jpg +classroom/scuola06_4.jpg +clothingstore/1470195165_8499fd04bf.jpg +classroom/salle_reunion_joliot_b.jpg +mall/Scarpe_Italiane_Milan_Italy.jpg +subway/subway_0267.jpg +artstudio/artist_studio_30_06_altavista.jpg +bookstore/Libreria_03_09_altavista.jpg +deli/deli_148_17_flickr.jpg +kitchen/indoor_0542.jpg +deli/deli_21_12_yahoo.jpg +library/43407107_204b8504b5.jpg +tv_studio/studio11_102_.jpg +dentaloffice/dentista_75_19_flickr.jpg +florist/florist_46_10_flickr.jpg +laundromat/o_laundromat.jpg +museum/museo_151_05_flickr.jpg +operating_room/operating_room_25_01_altavista.jpg +church_inside/underground_55_06_flickr.jpg +auditorium/websterauditorium_75.jpg +kindergarden/image4251.jpg +stairscase/int36.jpg +tv_studio/set_tv_99_.jpg +auditorium/auditorium_ambiance_494x326_95.jpg +florist/floreria_05_17_flickr.jpg +videostore/blockbuster_04_14_altavista.jpg +bathroom/room31.jpg +bar/bar_0047.jpg +studiomusic/control_console_right_1.jpg +bathroom/bano3_2.jpg +hairsalon/peluqueria1_2.jpg +waitingroom/url_1.jpg +elevator/elevator_google_0087.jpg +videostore/videoteka_04_03_altavista.jpg +church_inside/methodist_june04_02s.jpg +winecellar/bodega2.jpg +nursery/chambre_enfant2R.jpg +clothingstore/boutique_wide.jpg +elevator/elevator_google_0011.jpg +locker_room/locker_room_google_0188.jpg +meeting_room/conf04.jpg +trainstation/gare_145_04_flickr.jpg +gym/fieldhouse_weightroom.jpg +corridor/corridora5.jpg +fastfood_restaurant/CamilesCafe01.jpg +bar/bar_0146.jpg +laundromat/waschsalon_klein.jpg +library/ins18.jpg +bathroom/b2.jpg +cloister/Claustro_de_San_Juan_de_los_Reyes_II.jpg +hospitalroom/hospital_room_10.jpg +warehouse/warehouse_0206.jpg +casino/casino_0012.jpg +elevator/elevator_google_0010.jpg +computerroom/Lab_de_Informatica_1.jpg +bar/bar_0041.jpg +florist/floreria_08_16_flickr.jpg +movietheater/10_auditorium_1__1.jpg +closet/WH_Raised_Panel_RI.jpg +trainstation/gare_165_15_flickr.jpg +dining_room/d15a.jpg +hospitalroom/ashley2007_birth_002.jpg +kitchen/cdmc1167.jpg +studiomusic/trabajo_238115785.jpg +library/library466.jpg +restaurant/int655.jpg +hairsalon/dscn2934.jpg +cloister/kelso_cloister.jpg +grocerystore/a20071138236957.jpg +elevator/elevator_google_0027.jpg +kindergarden/events7.jpg +movietheater/2535570691_3518c2a636_1__8.jpg +bathroom/room267.jpg +clothingstore/Louise_Billgert_35277_RGB_72DPI_2.jpg +grocerystore/Rachel_at_grocery_store.jpg +deli/deli_100_17_flickr.jpg +videostore/blockbuster_42_21_flickr.jpg +florist/florist_52_08_flickr.jpg +kindergarden/tourKINDERGARTEN.jpg +toystore/Spielzeug_62_10_flickr.jpg +airport_inside/airport_inside_0113.jpg +shoeshop/Shoe_side_of_shop_SVL.jpg +subway/subway_0140.jpg +auditorium/costa_atlantica_navire_amphitheatre_1__144.jpg +operating_room/surgery_room_14_09_altavista.jpg +stairscase/Balcony_Epoch.jpg +pantry/despensa_133_09_flickr.jpg +poolinside/pool_inside_36_06_altavista.jpg +prisoncell/jailtour002.jpg +deli/deli_45_23_flickr.jpg +gym/gym2.jpg +laboratorywet/labmartiniempty.jpg +bar/bar_0482.jpg +classroom/classroom2.jpg +classroom/118463700_ead3ee32e3.jpg +children_room/kids_in_the_playroom_44_.jpg +bookstore/bookstore_31_03_flickr.jpg +clothingstore/crystal_shop.jpg +corridor/hall50.jpg +prisoncell/Jailcell2323.jpg +bowling/bowling_0048.jpg +church_inside/pantry_55_13_flickr.jpg +computerroom/IM000835.jpg +children_room/AT_04_04_2000_78_l.jpg +dining_room/easyst034.jpg +poolinside/piscina_cubierta_03_03_altavista.jpg +church_inside/kirche_mogelsberg.jpg +hairsalon/11571232651.jpg +laundromat/noosa_124.jpg +bowling/bowling_0053.jpg +dining_room/d2.jpg +bedroom/child4.jpg +inside_bus/inside_bus_091.jpg +toystore/toys_store_43_03_altavista.jpg +warehouse/warehouse_0046.jpg +children_room/PO_06_03_3000_76_l.jpg +jewelleryshop/newyork_caron_boutique.jpg +jewelleryshop/park_jewellers.jpg +library/neilson_hays_library02.jpg +fastfood_restaurant/B_CInside.jpg +warehouse/warehouse_0196.jpg +buffet/food_on_table.jpg +dining_room/dining048.jpg +inside_subway/inside_subway_0113.jpg +shoeshop/shoes_shop_29_20_altavista.jpg +inside_bus/inside_bus_089.jpg +greenhouse/green01.jpg +kindergarden/classroom2.jpg +buffet/buffet06.jpg +garage/tractor_barn_inside.jpg +grocerystore/shop16.jpg +lobby/AT_04_03_2001_03_l.jpg +library/library4.jpg +locker_room/locker_room_google_0065.jpg +bookstore/bookstore_10_02_altavista.jpg +computerroom/computer_lab333.jpg +grocerystore/organic_food_for_web.jpg +laboratorywet/wet_lab_06_07_altavista.jpg +dentaloffice/dentista_85_07_flickr.jpg +toystore/Spielzeug_145_06_flickr.jpg +auditorium/auditorium_mc2_1__94.jpg +jewelleryshop/dsc00562_12_.jpg +bowling/bowling_0009.jpg +waitingroom/waiting_room_32_10_altavista.jpg +auditorium/salle_amphitheatre3_1__170.jpg +restaurant_kitchen/restaurant_kitchen_google_0021.jpg +bar/bar_0025.jpg +bowling/bowling_0107.jpg +warehouse/warehouse_0121.jpg +computerroom/computer_room07.jpg +corridor/pasillo_interior_1_t.jpg +inside_bus/inside_bus_027.jpg +nursery/2007_05_01_replace3.jpg +florist/c04652600522c7ad139144ca0e2b_1_4.jpg +lobby/sLobby29.jpg +toystore/store3sm.jpg +laboratorywet/laboratorio_quimica_12_16_altavista.jpg +museum/museum_31_15_altavista.jpg +auditorium/affich_1__76.jpg +classroom/SALA_DE_AULA_DCNAT.jpg +cloister/esglesiavic21of1cc1.jpg +dentaloffice/dentista_32_04_altavista.jpg +closet/WC_Hutch.jpg +dining_room/dining31.jpg +trainstation/gare_160_22_flickr.jpg +artstudio/art_painting_studio_25_03_altavista.jpg +casino/casino_0083.jpg +corridor/coll_couloir.jpg +bookstore/bookstore_36_02_flickr.jpg +livingroom/indoor_0463.jpg +nursery/monkey_nursery_2.jpg +pantry/pantry_76_14_flickr.jpg +office/n457045.jpg +warehouse/warehouse_0018.jpg +bowling/bowling_0024.jpg +garage/20071218200319_garaje.jpg +auditorium/auditorium_101.jpg +fastfood_restaurant/442a2fd659407_81_1.jpg +children_room/dsc02536_main_full_14_.jpg +gym/salle_cardio_grand.jpg +toystore/Spielzeug_41_09_flickr.jpg +church_inside/Interieur_Gris.jpg +waitingroom/waiting_room_04_02_altavista.jpg +pantry/pantry_63_16_flickr.jpg +warehouse/warehouse_0102.jpg +waitingroom/waitingroom1.jpg +inside_subway/inside_subway_0020.jpg +locker_room/locker_room_google_0106.jpg +nursery/img_nursery_planner.jpg +locker_room/locker_room_google_0103.jpg +tv_studio/dscn0726_39_.jpg +concert_hall/allenroom.jpg +trainstation/train_station_03_08_altavista.jpg +stairscase/N457074.jpg +subway/subway_0109.jpg +bedroom/b3.jpg +winecellar/wine_cellar_30_20_altavista.jpg +greenhouse/greenhouse_452.jpg +hospitalroom/hospital061113_3_560.jpg +hospitalroom/SUDEEP_HOSPITAL3.jpg +children_room/HO_00_02_5271_23_l.jpg +hospitalroom/P1000698.jpg +meeting_room/conf13.jpg +restaurant_kitchen/restaurant_kitchen_google_0069.jpg +waitingroom/patient_waiting_room.jpg +florist/floreria_05_11_flickr.jpg +subway/subway_0012.jpg +toystore/a1_toystore1.jpg +museum/museo_163_01_flickr.jpg +jewelleryshop/1p.jpg +nursery/baby_room_012.jpg +jewelleryshop/inst_ilum_nego_joyeria.jpg +bookstore/librairie_wallonie_bruxelles_003.jpg +prisoncell/jzleoben_21.jpg +bookstore/bookstore_41_18_altavista.jpg +closet/BedroomCloset.jpg +clothingstore/ClothingStore.jpg +buffet/413224656_cd51fb5f4d.jpg +stairscase/N26m.jpg +jewelleryshop/1_1_.jpg +lobby/IM004853_JPG.jpg +inside_bus/inside_bus_015.jpg +children_room/AT_98_3_945_14_l.jpg +toystore/speelgoed_66_08_flickr.jpg +winecellar/bodega_17_19_flickr.jpg +cloister/claustro_de_la_catedral_1224_2.jpg +hairsalon/thumb52.jpg +museum/museo_105_12_flickr.jpg +warehouse/warehouse_0076.jpg +winecellar/bodega_135_18_flickr.jpg +children_room/OR_99_4_4922_07A_l.jpg +gameroom/AT_00_01_8142_36A_l.jpg +laboratorywet/wet_lab_11_19_altavista.jpg +elevator/elevator_google_0036.jpg +cloister/96847179_5961f50b85.jpg +dining_room/dining023.jpg +shoeshop/zapateria_22_10_flickr.jpg +prisoncell/prison_cell_07_02_altavista.jpg +tv_studio/antena_3_noticias_2004_noticias_3_rafaga_11052004_dvd30015_26_01_29_.jpg +inside_subway/inside_subway_0235.jpg +poolinside/schwimmen03_g.jpg +greenhouse/greenhouse_main.jpg +concert_hall/City_Halls.jpg +kitchen/kitchen032.jpg +buffet/buffet08.jpg +bakery/best_bakery_01_04_altavista.jpg +restaurant/restaurante_19_15_altavista.jpg +elevator/elevator_google_0060.jpg +casino/casino_0144.jpg +library/ins21.jpg +movietheater/14333_dsc02540_1__4.jpg +winecellar/bodega_64_22_flickr.jpg +trainstation/gare_20_08_flickr.jpg +inside_bus/inside_bus_017.jpg +bookstore/Libreria_20_14_altavista.jpg +studiomusic/Music_Studio_150_dpi.jpg +pantry/pantry_59_17_flickr.jpg +corridor/c11.jpg +grocerystore/h_3_1103585_1209394189.jpg +shoeshop/f338.jpg +inside_subway/inside_subway_0156.jpg +kindergarden/Preschool_Classroom.jpg +winecellar/wine_cellar_24_11_altavista.jpg +bathroom/indoor_0513.jpg +bowling/bowling_0152.jpg +computerroom/373437852_4e3926fe0c.jpg +nursery/babyroom005.jpg +restaurant_kitchen/restaurant_kitchen_google_0060.jpg +computerroom/jpg_im17.jpg +hospitalroom/WFUniversity_Hospital.jpg +poolinside/pool_inside_01_17_altavista.jpg +greenhouse/fotos2433.jpg +museum/museum_43_04_altavista.jpg +clothingstore/meatpack_boutique2.jpg +buffet/image_services_restaurant_buffet_1.jpg +bathroom/bathroom35.jpg +buffet/Buffet_of_Portuguese_food.jpg +bathroom/bath98.jpg +kindergarden/Picture_042.jpg +bowling/bowling_0157.jpg +bedroom/rose_bed_room.jpg +classroom/Imagen_008.jpg +lobby/lobby34.jpg +bar/bar_0046.jpg +cloister/claustro_catedral_oporto.jpg +computerroom/ComputerRoom_full.jpg +deli/deli_147_24_flickr.jpg +florist/florist_31_08_flickr.jpg +prisoncell/415957860_b226b6b23e.jpg +casino/casino_0016.jpg +garage/garage.jpg +deli/deli_128_21_flickr.jpg +kitchen/cdmc1164.jpg +bathroom/indoor_0045.jpg +fastfood_restaurant/pizza_hut_big.jpg +auditorium/amphivide_1__87.jpg +bar/bar_0285.jpg +locker_room/locker_room_google_0195.jpg +inside_bus/inside_bus_060.jpg +meeting_room/conf01.jpg +lobby/sLobby22.jpg +museum/museo_01_13_altavista.jpg +stairscase/AT_98_3_0944_36_l.jpg +tv_studio/tibetantv_studio48_1_75_.jpg +museum/museo_08_13_altavista.jpg +nursery/IM_137611_1.jpg +corridor/p1010066_c.jpg +bar/bar_0458.jpg +casino/casino_0097.jpg +florist/image002.jpg +poolinside/141821195_M.jpg +jewelleryshop/Interior_2.jpg +bowling/bowling_0020.jpg +office/room205.jpg +office/n457030.jpg +restaurant/restaurante_35_05_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0057.jpg +bathroom/indoor_0264.jpg +kindergarden/Preschool_art.jpg +warehouse/warehouse_0207.jpg +movietheater/121880878_93cb1c9058.jpg +gym/gimnasio_68_19_flickr.jpg +laundromat/lavanderia_77_06_flickr.jpg +operating_room/operating_room_18_06_altavista.jpg +elevator/elevator_google_0007.jpg +kindergarden/preschool_chairs_2006_2.jpg +buffet/buffet05.jpg +toystore/jugueteria_19_22_flickr.jpg +trainstation/gare_94_09_flickr.jpg +stairscase/Treppe_1.jpg +classroom/IGLOBE.jpg +clothingstore/Tibi_Boutique_1_Credit_Ad.jpg +lobby/lobby0002.jpg +office/office9.jpg +mall/1782523123_756a565e97.jpg +laboratorywet/laboratorio_quimica_14_04_altavista.jpg +museum/museo_128_11_flickr.jpg +gameroom/gameroom_1.jpg +kindergarden/ps_room_475x335.jpg +library/ins19.jpg +restaurant_kitchen/restaurant_kitchen_google_0002.jpg +gym/punttis_gym_overview.jpg +bookstore/bookstore_142_11_flickr.jpg +library/biblio01.jpg +casino/casino_0129.jpg +computerroom/computer_room1.jpg +locker_room/locker_room_google_0124.jpg +artstudio/artist_studio_36_17_altavista.jpg +buffet/1684965730_e82d832010_b.jpg +classroom/salleXL.jpg +mall/mall09.jpg +children_room/AT_04_02_3000_46_l.jpg +corridor/corridora3.jpg +garage/mso1012_ClothesRackBike_bef_w609.jpg +laboratorywet/peterfeinph.jpg +dining_room/dining3.jpg +library/792089_e681119c26.jpg +movietheater/CYP0101072_P.jpg +kitchen/kitchen78.jpg +concert_hall/events1.jpg +fastfood_restaurant/chipolte2.jpg +movietheater/domealbertville1.jpg +nursery/000000.jpg +movietheater/INP172655C_P.jpg +studiomusic/studio16.jpg +church_inside/prty3dfhxo3.jpg +deli/deli_162_20_flickr.jpg +livingroom/living82.jpg +office/homeoff001.jpg +gym/gimnasio_18_07_altavista.jpg +winecellar/wine_cellar_expo.jpg +subway/subway_0456.jpg +inside_bus/inside_bus_023.jpg +kitchen/cdmc1178.jpg +pantry/pantry_117_01_flickr.jpg +elevator/elevator_google_0031.jpg +fastfood_restaurant/114543829_e8ef2f7e7e.jpg +corridor/pasillo_edificio_escuela__480x640_c.jpg +hairsalon/url.jpg +bar/bar_0201.jpg +hospitalroom/5816CareForPatient.jpg +dining_room/int682.jpg +studiomusic/estudio1b.jpg +gym/gimnasio_68_20_flickr.jpg +laboratorywet/wet_lab_12_10_altavista.jpg +toystore/jugueteria_01_17_flickr.jpg +corridor/p1010079_c.jpg +concert_hall/url.jpg +stairscase/stairs06.jpg +grocerystore/2007_03_wfo11.jpg +studiomusic/int804.jpg +shoeshop/192622.jpg +church_inside/metropolitana_134_02_flickr.jpg +gameroom/gameroom_inside2.jpg +laboratorywet/laboratorio_quimica_07_04_altavista.jpg +movietheater/salle_20cinema_1__62.jpg +kitchen/kitchen086.jpg +poolinside/022506D.jpg +restaurant/restaurante_46_08_altavista.jpg +tv_studio/be5bd13e_1fb4_4dde_8d38_e29dda3815cb_81_.jpg +inside_bus/inside_bus_086.jpg +livingroom/int566.jpg +gym/gimnasio_78_14_flickr.jpg +restaurant_kitchen/restaurant_kitchen_google_0008.jpg +dining_room/dining005.jpg +florist/florist_59_21_flickr.jpg +hairsalon/pagiovanni.jpg +livingroom/indoor_0326.jpg +lobby/800px_DirkvdM_panama_hotel_lobby_1.jpg +restaurant_kitchen/restaurant_kitchen_google_0073.jpg +stairscase/kenngott_treppe_buche_sondergelaender.jpg +fastfood_restaurant/quiznos3.jpg +corridor/IMGP3211.jpg +shoeshop/zapateria_06_11_flickr.jpg +buffet/233827470_93eda76dee.jpg +concert_hall/Music_Concert_Hall_2.jpg +gameroom/fac_gameroom.jpg +studiomusic/novedades639.jpg +auditorium/auditor_1__90.jpg +restaurant_kitchen/restaurant_kitchen_google_0045.jpg +lobby/SD_00_01_51257_09_l.jpg +auditorium/p1010008_73cb7_1__65.jpg +restaurant/Restaurant_2.jpg +computerroom/Computer8i7.jpg +locker_room/locker_room_google_0240.jpg +airport_inside/airport_inside_0100.jpg +laundromat/4_vono.jpg +laboratorywet/laboratorio_quimica_17_12_altavista.jpg +concert_hall/g_vigoenfotos_1934d.jpg +hospitalroom/hospital_room_08_14_altavista.jpg +tv_studio/studio09_63_.jpg +auditorium/auditorium_560_43.jpg +bowling/bowling_0077.jpg +trainstation/gare_148_17_flickr.jpg +office/home_office.jpg +laundromat/lavanderia_25_07_flickr.jpg +shoeshop/shoes_shop_18_19_altavista.jpg +airport_inside/airport_inside_0274.jpg +gym/web_cardio_theatre_gym.jpg +kindergarden/100_1356_0096.jpg +concert_hall/asplundh_concert_hall.jpg +laundromat/lavanderia_96_20_flickr.jpg +computerroom/cyb_salle_pc_1.jpg +closet/graphic_home.jpg +office/corneroffice.jpg +kitchen/kitchen006.jpg +locker_room/locker_room_google_0061.jpg +subway/subway_0073.jpg +library/opacs.jpg +kitchen/dsc04183.jpg +videostore/videoclub_01_15_flickr.jpg +bookstore/Librairie_28_01_altavista.jpg +tv_studio/tv_studio_21_02_altavista.jpg +grocerystore/APRIL242002FakeGroceryStore.jpg +artstudio/art_painting_studio_10_15_altavista.jpg +pantry/pantry_165_16_flickr.jpg +warehouse/warehouse_0467.jpg +library/Day100006web.jpg +bathroom/room318.jpg +inside_subway/inside_subway_0037.jpg +waitingroom/salle_attente3.jpg +poolinside/spa_480x320.jpg +hospitalroom/01HospitalRoom.jpg +kitchen/kitchen136.jpg +movietheater/p1010975_1__28.jpg +winecellar/wine_storage_16_09_altavista.jpg +bowling/bowling_0139.jpg +gameroom/gameroom_05_400.jpg +laboratorywet/wet_lab_30_15_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0091.jpg +movietheater/tmpa4d7c0f.jpg +dining_room/indoor_0315.jpg +bookstore/Libreria_19_03_altavista.jpg +bedroom/int855.jpg +jewelleryshop/281208.jpg +waitingroom/lobby2.jpg +auditorium/6a00d834515beb69e200e55378baa58834_800wi_1__16.jpg +bookstore/Libreria_15_07_altavista.jpg +computerroom/COMPUTER_2520ROOM_2520PICTURE2.jpg +grocerystore/shop30.jpg +laboratorywet/wet_lab_43_17_altavista.jpg +bowling/bowling_0086.jpg +greenhouse/ferns4.jpg +operating_room/surgery_room_30_14_altavista.jpg +closet/closet_organizer.jpg +gym/gimnasio_167_13_flickr.jpg +cloister/cloister1.jpg +meeting_room/i_f_small_conference_room.jpg +pantry/pantry_33_15_flickr.jpg +casino/casino_0228.jpg +kindergarden/langlib.jpg +lobby/marina_terra_hotel_lobby.jpg +buffet/warwick_buffet_cater_catering_food_1960_1960s_retro_kf5837.jpg +cloister/Cloister221.jpg +movietheater/sala_de_cine4.jpg +nursery/rowans_nursery_baby_room_1.jpg +toystore/jugueteria_08_21_flickr.jpg +nursery/nursery2thumb.jpg +bathroom/IMG_1313.jpg +shoeshop/zapateria_04_02_flickr.jpg +videostore/StoreWeb.jpg +jewelleryshop/12_1.jpg +prisoncell/prison_cell_05_14_altavista.jpg +classroom/earthquake_relief1.jpg +bakery/bakery_02_03_yahoo.jpg +office/ins2.jpg +shoeshop/shoes_shop_47_04_altavista.jpg +trainstation/estacion_de_ferrocarriles_44_19_altavista.jpg +library/inside01.jpg +kindergarden/pre_school_6_66104926_std.jpg +buffet/cimg3556_1.jpg +lobby/lobby5.jpg +pantry/02_pantry_storage_organization_lg.jpg +subway/subway_0242.jpg +bookstore/bookstore_full.jpg +poolinside/piscina_cubierta_11_11_altavista.jpg +waitingroom/11.jpg +laundromat/lavanderia_77_05_flickr.jpg +dentaloffice/dental_office_15_17_altavista.jpg +inside_subway/inside_subway_0115.jpg +hairsalon/6988.jpg +lobby/sLobby11.jpg +shoeshop/facade_gd.jpg +children_room/AT_99_6_8883_12_l.jpg +tv_studio/tv_studio_40_01_altavista.jpg +trainstation/gare_32_02_flickr.jpg +fastfood_restaurant/Siem_Reap_airport_Dairy_Queen.jpg +bar/bar_0231.jpg +casino/casino_0001.jpg +nursery/7_chambre_enfant360.jpg +mall/ShopView_Gesundbrunnen.jpg +museum/museo_138_19_flickr.jpg +airport_inside/airport_inside_0123.jpg +grocerystore/groceries3_main_Full.jpg +toystore/toys_store_16_01_altavista.jpg +office/o8.jpg +garage/IMG_2145.jpg +livingroom/living71.jpg +locker_room/locker_room_google_0043.jpg +bathroom/indoor_0078.jpg +dentaloffice/dentista_119_22_flickr.jpg +elevator/elevator_google_0015.jpg +livingroom/roomscan11.jpg +computerroom/cip_pool.jpg +elevator/elevator_google_0042.jpg +operating_room/surgery_room_07_19_altavista.jpg +airport_inside/airport_inside_0371.jpg +elevator/elevator_google_0084.jpg +office/o7.jpg +operating_room/surgery_room_31_13_altavista.jpg +closet/closet9713.jpg +livingroom/room368.jpg +corridor/p1010077_c.jpg +grocerystore/store_fruit_counter.jpg +lobby/Locker_Room.jpg +airport_inside/airport_inside_0316.jpg +kitchen/kitchen004.jpg +fastfood_restaurant/DSC00476.jpg +livingroom/room498.jpg +auditorium/b76521a_3966ea3_b_1__139.jpg +bedroom/indoor_0057.jpg +prisoncell/prison_cell_baby_child.jpg +bedroom/masterbed2.jpg +casino/casino_0362.jpg +inside_subway/inside_subway_0053.jpg +greenhouse/greenhouse_fullgrowth.jpg +lobby/MBASE_historia.jpg +bar/bar_0426.jpg +bookstore/Libreria_17_03_altavista.jpg +grocerystore/08082003_aisle.jpg +bowling/bowling_0001.jpg +clothingstore/st_joseph_michigan_shopping_fashion_boutique_clothing3.jpg +dentaloffice/dentista_03_17_altavista.jpg +subway/subway_0355.jpg +inside_subway/inside_subway_0018.jpg +museum/museo_16_22_flickr.jpg +library/Orlando_library_090.jpg +cloister/lacock_cloister.jpg +corridor/n457061.jpg +inside_bus/inside_bus_012.jpg +hospitalroom/DSC01811.jpg +office/int77.jpg +library/Fairfield_Pub_Library_A.jpg +lobby/Le_Meridien_Kathmandu_Hotel_Lobby_Sep_2005.jpg +poolinside/piscina_cubierta_12_11_altavista.jpg +inside_bus/inside_bus_052.jpg +toystore/keeling_market20.jpg +trainstation/train_station_48_05_altavista.jpg +winecellar/bodega_04_03_altavista.jpg +concert_hall/1217_2_1000_Buro_Perth_Concert_Hall_2.jpg +restaurant/restaurant_15_18_altavista.jpg +buffet/ricetable.jpg +florist/star_florist.jpg +airport_inside/airport_inside_0453.jpg +greenhouse/greenhouse042.jpg +bakery/bakery_11_14_yahoo.jpg +garage/garage187.jpg +jewelleryshop/94_13.jpg +fastfood_restaurant/6.jpg +gym/media39989.jpg +laundromat/lavanderia_53_13_flickr.jpg +shoeshop/marky_png.jpg +poolinside/1637_7502M.jpg +livingroom/living67.jpg +warehouse/warehouse_0171.jpg +artstudio/art_painting_studio_51_15_altavista.jpg +church_inside/Poitiers_Church_of_Notre_Dame_de_la_Grande_IMG_4877.jpg +hospitalroom/IMG_1313.jpg +stairscase/IMG_7676.jpg +toystore/Spielzeug_35_09_flickr.jpg +hospitalroom/habitacion_hospital_06_10_altavista.jpg +laboratorywet/laboratorio_quimica_06_05_altavista.jpg +airport_inside/airport_inside_0244.jpg +gym/gimnasio_26_15_flickr.jpg +studiomusic/control1.jpg +bookstore/Libreria_35_17_altavista.jpg +hospitalroom/IMG_0042.jpg +studiomusic/studio21.jpg +inside_bus/inside_bus_028.jpg +trainstation/gare_07_11_flickr.jpg +hairsalon/hair_total.jpg +shoeshop/3149_1_empresa1.jpg +artstudio/art_painting_studio_39_06_altavista.jpg +classroom/salle_24.jpg +concert_hall/concerthall_empty.jpg +waitingroom/Waiting_Room_1.jpg +auditorium/la_salle_de_l_auditorium_imagelarge_1__154.jpg +trainstation/train_station_37_10_altavista.jpg +bookstore/bookstore_25_20_altavista.jpg +inside_subway/inside_subway_0280.jpg +bar/bar_0131.jpg +buffet/308782383_1400880090.jpg +gym/gimnasio_46_18_altavista.jpg +fastfood_restaurant/colorado_springs_chipotle6.jpg +buffet/Sunday_Dinner.jpg +gym/url.jpg +corridor/p1010078_c.jpg +mall/mall40.jpg +prisoncell/jailcell333.jpg +studiomusic/susustudio.jpg +inside_subway/inside_subway_0359.jpg +stairscase/1115735139.jpg +concert_hall/OnondagaCivicCenter.jpg +children_room/bec438b5_c449_400d_a0b3_e9f9309c0d7e_player_41_.jpg +locker_room/locker_room_google_0245.jpg +restaurant/int60.jpg +gameroom/salle_de_jeux_09_05_altavista.jpg +gameroom/gameroom_good.jpg +mall/alamanda.jpg +studiomusic/117989573_65294a6fcb.jpg +gameroom/salle_de_jeux441.jpg +office/o6.jpg +classroom/Japanese_classroom.jpg +grocerystore/WEB2.jpg +buffet/Buffet_Lettuce_gif.jpg +tv_studio/cd_teleideal_108_33_.jpg +bedroom/indoor_0515.jpg +dining_room/interior005.jpg +hairsalon/salon1687.jpg +studiomusic/Studiow560h420.jpg +restaurant_kitchen/restaurant_kitchen_google_0039.jpg +trainstation/room212.jpg +concert_hall/canopy.jpg +pantry/pantry_106_16_flickr.jpg +library/1093023.jpg +children_room/AT_99_1_7838_12A_l.jpg +gym/fitness_center3.jpg +stairscase/roomscan29.jpg +bookstore/125_115_LibrairieNas.jpg +classroom/Level_6_C_classroom.jpg +bar/bar_0233.jpg +grocerystore/167613_3.jpg +cloister/525066.jpg +hairsalon/salon10.jpg +operating_room/surgery_room_30_08_altavista.jpg +hairsalon/Salon1111.jpg +restaurant/bistro_restaurant_for.jpg +bakery/boulangerie_48_07_yahoo.jpg +studiomusic/music-studio.jpg +elevator/elevator_google_0050.jpg +livingroom/living38.jpg +winecellar/bodega_101_07_flickr.jpg +locker_room/locker_room_google_0143.jpg +winecellar/cave_champagne_06_08_altavista.jpg +restaurant/Bertucci_01_lg.jpg +prisoncell/036f.jpg +clothingstore/86_Bookstore1.jpg +elevator/elevator_google_0102.jpg +nursery/baby_room_and_jenny_722549.jpg +bar/bar_0132.jpg +inside_bus/inside_bus_034.jpg +pantry/Closet_off_of_kitchen.jpg +children_room/adler_pink_kids_room.jpg +library/PCPL_gen.jpg +corridor/hallway1_c.jpg +hairsalon/111528037_7494ae335f.jpg +closet/One_of_2_Closets_in_Master_Bedroom_A_Walk_In_Closet.jpg +stairscase/N457072.jpg +movietheater/movietheater_google_0010.jpg +winecellar/wine_cellar_40_18_altavista.jpg +bowling/bowling_0090.jpg +clothingstore/FotoLoj_ID1_2007_3_30_L1030672.jpg +children_room/kids_playroom_zurich_45_.jpg +deli/deli_131_15_flickr.jpg +hairsalon/willkommen.jpg +bedroom/IMG_2127.jpg +closet/main_closet.jpg +restaurant/photos_salle1.jpg +restaurant_kitchen/restaurant_kitchen_google_0066.jpg +church_inside/FreeFoto_castle_30_33.jpg +dentaloffice/dentista_oficina_01_07_flickr.jpg +lobby/lobby121211.jpg +bar/bar_0194.jpg +laboratorywet/wet_lab_08_10_altavista.jpg +office/despacho3_400.jpg +stairscase/stairs02.jpg +bowling/bowling_0058.jpg +restaurant/restaurant_06_10_altavista.jpg +concert_hall/west1.jpg +jewelleryshop/colonialjewelers41_32_.jpg +restaurant/url_2.jpg +dentaloffice/dental_office.jpg +mall/full_shopping_Central_1.jpg +stairscase/D16a.jpg +winecellar/wine_cellar_03_03_altavista.jpg +office/cover_image_dreamoffice.jpg +locker_room/locker_room_google_0034.jpg +garage/garage_02.jpg +gameroom/sala_de_juegos_08_20_altavista.jpg +kindergarden/preschool_room.jpg +meeting_room/c11.jpg +garage/vacation_home_57garage.jpg +operating_room/operating_room_27_01_altavista.jpg +buffet/060926buffet_560.jpg +computerroom/IMG_1144.jpg +trainstation/estacion_de_ferrocarriles_39_16_altavista.jpg +bathroom/indoor_0242.jpg +videostore/blockbuster_38_01_altavista.jpg +office/va_02_05_9775_04_l.jpg +buffet/322497197_a67f529fbe.jpg +artstudio/art_painting_studio_36_06_altavista.jpg +toystore/jugueteria_15_10_flickr.jpg +casino/casino_0324.jpg +concert_hall/ava_theater.jpg +subway/subway_0288.jpg +kitchen/cdmc1289.jpg +laboratorywet/wet_lab_24_11_altavista.jpg +tv_studio/tv_studio876_69_.jpg +inside_subway/inside_subway_0175.jpg +bookstore/Libreria_49_02_altavista.jpg +laundromat/lavanderia_48_01_flickr.jpg +subway/subway_0463.jpg +trainstation/train_station_47_06_altavista.jpg +deli/deli_63_04_flickr.jpg +gameroom/AT_99_2_7450_20_l.jpg +bookstore/OakParkFrontView.jpg +gameroom/gameroom688.jpg +movietheater/sala_de_cine_13_03_altavista.jpg +mall/hillsdale_mall_photo2.jpg +prisoncell/jail02.jpg +artstudio/artist_studio_12_14_altavista.jpg +subway/subway_0257.jpg +bathroom/bath.jpg +waitingroom/JubileeMeetingRoom.jpg +hospitalroom/050622_13x.jpg +garage/dbl_garage_interior.jpg +clothingstore/Boutique_Takenoko.jpg +cloister/525014.jpg +livingroom/ph_02_03_4675_04_l.jpg +pantry/pantry_01_11_flickr.jpg +studiomusic/estudio.jpg +toystore/url56.jpg +florist/050621_florist2.jpg +concert_hall/concertgebouw2005.jpg +waitingroom/sLobby09.jpg +museum/museum_33_14_altavista.jpg +shoeshop/shoes_shop_32_06_altavista.jpg +warehouse/warehouse_0062.jpg +concert_hall/slide7.jpg +library/bibliotheque55.jpg +children_room/img_0194_21_.jpg +mall/wMallAmInt2_ep.jpg +meeting_room/n457018.jpg +bookstore/Libreria_08_02_altavista.jpg +concert_hall/concerthall01.jpg +inside_bus/inside_bus_099.jpg +museum/museum_10_11_flickr.jpg +office/office5.jpg +gameroom/SalleDeJeux.jpg +movietheater/img_497_819_1__23.jpg +dining_room/dinner01.jpg +locker_room/locker_room_google_0205.jpg +bookstore/bookstore_45_03_altavista.jpg +grocerystore/url_1.jpg +bowling/bowling_0103.jpg +concert_hall/PAC_interior.jpg +restaurant_kitchen/restaurant_kitchen_google_0024.jpg +hairsalon/_PZF03ZQH3ZmH0Av0kYGRg.jpg +laboratorywet/1_analytical_laboratories.jpg +movietheater/movietheater_google_0042.jpg +corridor/couloir01.jpg +toystore/speelgoed_40_18_flickr.jpg +kindergarden/prek2.jpg +lobby/sLobby20.jpg +museum/museo_86_05_flickr.jpg +computerroom/2632.jpg +toystore/toys_store_27_19_altavista.jpg +bakery/boulangerie_30_11_altavista.jpg +concert_hall/Mecanoo_in_Kaohsiung_Taiwan_3.jpg +corridor/lv_02_04_10915_20_l.jpg +studiomusic/int771.jpg +bathroom/room357.jpg +restaurant/salle1.jpg +computerroom/Informatica4.jpg +children_room/AT_98_5_989_08_l.jpg +deli/deli_119_04_flickr.jpg +tv_studio/tv_studio2b_115_.jpg +videostore/videoclub_09_13_altavista.jpg +buffet/860304346_f61ad7eb22.jpg +deli/deli_111_17_flickr.jpg +shoeshop/Franchise500x375_1.jpg +artstudio/artist_studio_31_11_altavista.jpg +garage/garage_inside.jpg +kitchen/indoor_0440.jpg +artstudio/art_painting_studio_23_06_altavista.jpg +inside_bus/inside_bus_039.jpg +clothingstore/img_3242_tokyo_harajuku_jingu_mae_meiji_dori_mise_fashion_clothing_store_on_meiji_dori_harajuku_jingumae.jpg +fastfood_restaurant/melvados_gif.jpg +jewelleryshop/01.jpg +livingroom/room5.jpg +tv_studio/tv_am_studio_a_1983_tv_am_org_uk_400p_107_.jpg +bar/bar_0386.jpg +dining_room/Dining20.jpg +concert_hall/sydney_opera_house_concert_hall.jpg +livingroom/living75.jpg +laboratorywet/wet_lab_17_14_altavista.jpg +subway/subway_0351.jpg +trainstation/gare_125_22_flickr.jpg +tv_studio/tv_studio_06_08_altavista.jpg +restaurant_kitchen/restaurant_kitchen_google_0026.jpg +museum/museum_48_11_altavista.jpg +children_room/AT_99_2_8171_36_l.jpg +grocerystore/shop17.jpg +pantry/Blakes_26_.jpg +concert_hall/main_concert_hall_within.jpg +airport_inside/airport_inside_0085.jpg +hospitalroom/ICU.jpg +laundromat/470_144925.jpg +classroom/02_3_Day_Classroom_Overview.jpg +restaurant/restaurant_09_03_altavista.jpg +kitchen/kitchen226.jpg +elevator/elevator_google_0079.jpg +laundromat/Guest_Laundry_Room.jpg +livingroom/at_01_6b_5490_30a_l.jpg +restaurant/BistroMini.jpg +restaurant_kitchen/restaurant_kitchen_google_0099.jpg +kindergarden/classroom8.jpg +office/office6.jpg +bar/bar_0136.jpg +elevator/elevator_google_0037.jpg +locker_room/locker_room_google_0126.jpg +stairscase/stairs01.jpg +winecellar/bodega_69_16_flickr.jpg +bedroom/s61.jpg +classroom/salle_de_cours_2.jpg +garage/garage_interior1.jpg +inside_bus/inside_bus_085.jpg +deli/deli_45_19_flickr.jpg +gameroom/salle_de_jeux156.jpg +casino/casino_0387.jpg +computerroom/ComputerClassroom2.jpg +corridor/IMG_9415.jpg +museum/museo_10_09_altavista.jpg +operating_room/operating_room_24_16_altavista.jpg +bar/bar_0279.jpg +grocerystore/070707_15291.jpg +jewelleryshop/foto_joyeria.jpg +stairscase/room511.jpg +laundromat/lavanderia_53_23_flickr.jpg +prisoncell/Jail_Cell23.jpg +bookstore/bookstore_23_21_flickr.jpg +locker_room/locker_room_google_0244.jpg +movietheater/CCP0014467_P.jpg +bar/bar_0302.jpg +church_inside/metropolitana_36_18_flickr.jpg +restaurant/chambre_resto_3.jpg +children_room/children_playroom_12_.jpg +restaurant_kitchen/restaurant_kitchen_google_0072.jpg +livingroom/l11.jpg +pantry/pantry_40_03_flickr.jpg +closet/Duluth_CherryX.jpg +jewelleryshop/yampell_sidewall_30_.jpg +meeting_room/conf28.jpg +winecellar/cave_champagne_09_14_altavista.jpg +winecellar/wine_cellar_39_13_altavista.jpg +closet/MasterSuiteTrad.jpg +inside_subway/inside_subway_0338.jpg +bathroom/indoor_0368.jpg +deli/deli_119_19_flickr.jpg +dentaloffice/dentaire_01_22_flickr.jpg +laboratorywet/wet_lab_03_19_altavista.jpg +office/o2.jpg +concert_hall/Concertgebouw1.jpg +bar/bar_0439.jpg +kindergarden/classroom122.jpg +nursery/complete_baby_room_furnitures.jpg +classroom/Classroom010_full.jpg +computerroom/aula_informatica_gif.jpg +elevator/elevator_google_0083.jpg +kitchen/indoor_0262.jpg +dentaloffice/dentista_117_06_flickr.jpg +tv_studio/plato_de_television_01_03_altavista.jpg +restaurant/restaurante_06_08_altavista.jpg +gym/Gym432.jpg +bookstore/Libreria_19_02_altavista.jpg +library/students_library_computers.jpg +lobby/lobby2998.jpg +nursery/103ensembleb.jpg +bathroom/roomscan34.jpg +classroom/sala_de_aula.jpg +kindergarden/toddlers.jpg +prisoncell/carcel_79_22_flickr.jpg +museum/museum_43_03_altavista.jpg +trainstation/gare_83_16_flickr.jpg +florist/florist_03_01_flickr.jpg +gameroom/game_room.jpg +videostore/videoclub_07_15_flickr.jpg +stairscase/int696.jpg +church_inside/Kirche45.jpg +warehouse/warehouse_0126.jpg +dining_room/dining017.jpg +restaurant/restaurant_17_17_altavista.jpg +trainstation/gare_115_16_flickr.jpg +greenhouse/invernaderos_fotos_015.jpg +elevator/elevator_google_0055.jpg +livingroom/l12.jpg +movietheater/movietheater_google_0006.jpg +bakery/bakery_02_20_yahoo.jpg +inside_subway/inside_subway_0154.jpg +bakery/bakery_14_17_yahoo.jpg +dentaloffice/dentista_121_23_flickr.jpg +laboratorywet/laboratorio_quimica_01_18_flickr.jpg +church_inside/Columb_Eglise_int1.jpg +garage/9_large.jpg +buffet/central_buffet_inside.jpg +airport_inside/airport_inside_0141.jpg +bedroom/at_00_05_8293_15_l.jpg +clothingstore/501005839_primary.jpg +restaurant/restaurant_27_17_altavista.jpg +cloister/Refect_Cloister_Jun04_D3613sAR.jpg +toystore/toys_store_27_05_altavista.jpg +garage/IMG_0122.jpg +nursery/nursery3.jpg +computerroom/computerroom03.jpg +movietheater/p1010981_1__30.jpg +warehouse/warehouse_0194.jpg +pantry/pantry_87_02_flickr.jpg +bathroom/room280.jpg +florist/florist_43_02_altavista.jpg +prisoncell/7579543Cruise094.jpg +children_room/playroom1_53_.jpg +elevator/elevator_google_0016.jpg +jewelleryshop/5b_tesorini_9_.jpg +bookstore/bookstore_39_06_altavista.jpg +trainstation/estacion_de_ferrocarriles_28_09_altavista.jpg +dining_room/dining058.jpg +inside_bus/inside_bus_081.jpg +videostore/video_store_10_15_altavista.jpg +classroom/DCP_1461grande.jpg +gym/gym3.jpg +jewelleryshop/joyeria.jpg +subway/subway_0094.jpg +stairscase/int10.jpg +elevator/elevator_google_0025.jpg +videostore/videostore.jpg +tv_studio/tv_studio_18_08_altavista.jpg +clothingstore/Paris_Boutique_Inside_Photo_three_way_mirro.jpg +videostore/videoteca_02_18_flickr.jpg +warehouse/warehouse_0366.jpg +meeting_room/n457039.jpg +poolinside/piscina_cubierta_04_14_altavista.jpg +jewelleryshop/4.jpg +bedroom/b9.jpg +bookstore/Libreria_16_18_altavista.jpg +children_room/90906stacysplayroomafter004_w300h225_9_.jpg +bakery/bakery_07_17_yahoo.jpg +closet/closet_open.jpg +studiomusic/Studio1small.jpg +clothingstore/336110682_2e01b0f9be.jpg +kindergarden/IMG_1504_18202246_std.jpg +concert_hall/StageFulltag.jpg \ No newline at end of file diff --git a/aggregate_covariance.m b/aggregate_covariance.m new file mode 100755 index 0000000..17e5faf --- /dev/null +++ b/aggregate_covariance.m @@ -0,0 +1,40 @@ +% Carl Doersch (cdoersch at cs dot cmu dot edu) +% compute features for a set of images, and then the sufficient +% statistics needed for computing the mean and covariance matrix. +ntotal=0; +for(i=1:numel(dsidx)) + params=ds.conf.params; + I = im2double(getimg(ds,ds.myiminds(dsidx(i)))); + + if(dsfield(params,'imageCanonicalSize')) + [IS, scale] = convertToCanonicalSize(I, params.imageCanonicalSize); + else + IS=I; + end + pyramid = constructFeaturePyramidForImg(I, params); + pcs=round(ds.conf.params.patchCanonicalSize/ds.conf.params.sBins)-2; + [features, levels, indexes] = unentanglePyramid(pyramid, pcs,struct('normalizefeats',false)); + if(isempty(features)) + continue; + end + if(~exist('featsum','var')) + featsum=sum(features,1); + else + featsum=featsum+sum(features,1); + end + ntotal=ntotal+size(features,1); + if(~exist('dotsum','var')) + dotsum=features'*features; + else + dotsum=dotsum+features'*features; + end +end +if(ntotal==0) + return +end +ds.n{dsidx(1)}=ntotal; +ds.featsum{dsidx(1)}=featsum; +ds.dotsum{dsidx(1)}=dotsum; +for(i=1:numel(dsidx)) + ds.imgflags{dsidx(i)}=1; +end diff --git a/assigntoclosest.m b/assigntoclosest.m new file mode 100755 index 0000000..327d24c --- /dev/null +++ b/assigntoclosest.m @@ -0,0 +1,36 @@ +% efficient nearest-neighbors in Euclidean distance. +% each row of toassign is assigned to the nearest row in targets. +% closest(i) is the row-index in targets of the closest element +% for toassign(i,:). outdist(i) is the distance to that point. +function [closest,outdist]=assigntoclosest(toassign,targets,nonrm) + global ds; + if(isempty(targets)) + closest=[]; + outdist=[]; + return; + end + targsq=targets.^2;%sum(targets.^2,2); + closest=zeros(size(toassign,1),1); + outdist=zeros(size(toassign,1),1); + for(i=1:800:size(toassign,1)) + inds=i:min(i+800-1,size(toassign,1)); + batch=toassign(inds,:); + batchsq=sum(batch.^2,2); + inprod=targets*(batch'); + if(dsbool(ds.conf,'whiteningv2')||(exist('nonrm','var')&&nonrm)) + dist=inprod; + [outdist(inds),closest(inds)]=max(dist,[],1); + else + %dist=bsxfun(@plus,bsxfun(@minus,batchsq',2*inprod),targsq); + normval=sqrt(bsxfun(@rdivide,targsq*(batch'~=0),sum(batch'~=0,1))-bsxfun(@rdivide,(targets*(batch'~=0)).^2,sum(batch'~=0,1).^2)); + %normval=sqrt(targsq*(batch'~=0)-bsxfun(@rdivide,(targets*(batch'~=0)).^2,sum(batch'~=0,1))); + + normval(normval==0)=1; + dist=(-bsxfun(@rdivide,inprod,sum(batch'~=0,1))./normval); + %if(any(dist(:))<0) + %keyboard; + %end + [outdist(inds),closest(inds)]=min(dist,[],1); + end + end +end diff --git a/autoclust_opt_init.m b/autoclust_opt_init.m new file mode 100755 index 0000000..2b3bb39 --- /dev/null +++ b/autoclust_opt_init.m @@ -0,0 +1,82 @@ +% Carl Doersch (cdoersch at cs dot cmu dot edu) +% Initialize each detector in one batch. Initial weight vectors are in +% ds.detectors{dsidx}, and initial patch features are in ds.initFeats. +% For the most part, it's just copying the data in ds.initFeats into the +% detector structure, but it also needs to set the bias. It's important +% to give a reasonable initial estimate of the bias because if affects the +% number of patches mined in the early rounds, which can have a big impact +% on speed. + +posPats=dsload('ds.initPatches'); +dsload('ds.initFeats'); + +% Find the label for this batch and select a set of negative patches from +% ds.initFeats. These negatives are needed to calibrate each detector in the batch. +dsload('ds.imgs'); +dsload('ds.classperbatch'); +dsload('ds.batchfordetr'); +myclusts=ds.batchfordetr(ds.batchfordetr(:,2)==dsidx,1); +neginds=find(idxwithdefault(ds.imgs{ds.conf.currimset}.label,ds.initPatches(:,7),0)~=ds.classperbatch(dsidx)); +rand('seed',dsidx); +rp=randperm(numel(neginds)); +rp=rp(1:min(numel(rp),5000)); +negPats=ds.initPatches(neginds(rp),:); +initFeatsNeg=ds.initFeats(neginds(rp),:); + +% loop over the detectors, initializing each one. Note that ds.conf.params.graddescfun +% is called to actually do the initialization. +alldets=[]; +featstokeep={}; +for(i=1:size(ds.detectors{dsidx}.id,1)) + % Select a single detector. + ctr=effstridx(ds.detectors{dsidx},i); + disp(['optimizing: ' num2str(ctr.id)]); + + % Find the initial feature(s) for this patch (in this algorithm, it + % will be the single initially sampled patch). + data=[ds.initFeats(ds.initPatches(:,6)==myclusts(i),:);initFeatsNeg]; + + % Compute whether each patch is a positive (it's associated with this detector) + % or a negative (it came from initFeatsNeg). + lab=zeros(size(data,1),1); + lastpos=sum(ds.initPatches(:,6)==myclusts(i)); + lab(1:lastpos)=1; + + % Normalize and rescale the detector. TODO: this should probably + % be a parameter. + ctr.w=ctr.w/norm(ctr.w)*2; + ctr.b=.1; + + % Call the gradient descent function to do the initialization. + % doGradDescentProj will simply set ctr.b such that the constraint + % is satisfied. + [ctr_out_tmp,scores]=ds.conf.params.graddescfun(data',lab*2-1,[ctr.w ctr.b]',ones(size(lab')),0); + ctr.w=c(ctr_out_tmp(1:end-1))'; + ctr.b=ctr_out_tmp(end); + ctr_out(i)=ctr; + + % Read the scores produced by ds.conf.params.graddescfun for each patch + % and select only the ones above -.02. However, make sure that we have + % at least numel(ctr.w)/5; too few patches may cause us to degenerate later. + sscores=sort(scores,'descend'); + thr=min(-.02,sscores(min(ceil(numel(ctr.w)/5),numel(sscores)))); + dets=[posPats(ds.initPatches(:,6)==myclusts(i),:);negPats(scores((lastpos+1):end)>=thr,:)]; + featstokeep{end+1,1}=data([true(lastpos,1);(scores((lastpos+1):end)>=thr)'],:); + dets(:,6)=myclusts(i); + alldets=[alldets;dets]; + +end + +% write out the initialized detectors & cache the detections in the +% proper location. If ds.sys.distproc.localdir is set, write the +% cached features there. Note that this will "assign" this batch +% to one particular machine; any other machine that tries to access +% the cache will fail! +ds.round.detectors{dsidx}=str2effstr(ctr_out); +ds.round.prevdets{dsidx}=alldets; +if(dsfield(ds,'sys','distproc','localdir')) + prevfeats=cell2mat(featstokeep); + save([ds.sys.distproc.localdir 'prevfeats' num2str(dsidx) '_0.mat'],'prevfeats'); +else + ds.round.prevfeats{dsidx}=cell2mat(featstokeep); +end diff --git a/autoclust_optimize.m b/autoclust_optimize.m new file mode 100755 index 0000000..cc014e8 --- /dev/null +++ b/autoclust_optimize.m @@ -0,0 +1,155 @@ +dsload('ds.batchfordetr'); +dsload('ds.classperbatch'); +imgs=dsload('ds.imgs{ds.conf.currimset}'); +% load the detections that were used for training on the previous round. +% prevdets countains the bounding boxes; prevweights contains the alpha's +% from the paper, and prevfeats is the actual feature vectors. +prevdets=dsload(['ds.round.prevdets{' num2str(dsidx) '}'],'clear'); +dsload(['ds.round.prevweights{' num2str(dsidx) '}']); +if(isfield(ds.round,'prevweights')) + prevweights=ds.round.prevweights{dsidx}; + ds.round=rmfield(ds.round,'prevweights'); +else + prevweights=ones(size(prevdets,1),1); +end +dsload('ds.round.roundid'); +% if there's a local directory, load the features from there (we're guaranteed +% to be the only machine optimizing this detector). Otherwise they're +% stored in dswork. +if(dsfield(ds,'sys','distproc','localdir')) + if(ds.round.roundid>5) + % if you're running out of disk space you can delete data from old rounds...but note + % that this will make it impossible to back up. + delete([ds.sys.distproc.localdir 'prevfeats' num2str(dsidx) '_' num2str(ds.round.roundid-2) '.mat']); + end + load([ds.sys.distproc.localdir 'prevfeats' num2str(dsidx) '_' num2str(ds.round.roundid-1) '.mat']); +else + prevfeats=dsload(['ds.round.prevfeats{' num2str(dsidx) '}'],'clear'); +end + +dsload('ds.round.myiminds'); +mydetrs=ds.batchfordetr(ds.batchfordetr(:,2)==dsidx,1); + +% Discard any irrelevant patches from previous rounds. That generally means +% any patch from an image that we re-ran detection on during hte current round. +% The first patch in the list for each detector, however, is generally +% kept (control this behavior with the ds.round.exceptfirst flag). This patch +% is the one that was randomly sampled to initialize the cluster. This behavior +% is an artifact from versions of this codebase which treated the initial patch +% as special and broke if it disappeared. It's probably not needed, but +% I had this flag set for the experiments in the paper. +dsload('ds.round.discardprevpatches'); +dsload('ds.round.exceptfirst'); +tokeep=~ismember(prevdets(:,7),ds.round.myiminds); +[~,candidatepatches]=ismember(mydetrs,prevdets(:,6),'R2012a'); +if(~all(imgs.label(prevdets(candidatepatches,7))==ds.classperbatch(dsidx))) + error('classperbatch wrong'); +end +tokeep(candidatepatches)=true; +if(dsbool(ds.round,'discardprevpatches')) + tokeep(:)=false; + if(dsbool(ds.round,'exceptfirst')) + tokeep(candidatepatches)=true; + end +end +discardifnew=prevdets(candidatepatches(tokeep(candidatepatches)),[6:7]); +dets={prevdets(tokeep,:)}; +feats={prevfeats(tokeep,:)}; +allovlweight={prevweights(tokeep,:)}; +clear prevdets; +clear prevfeats; +clear prevweights; + +% Load the detections from the current round of detection. +for(i=1:numel(ds.round.myiminds)) + if(~isempty(ds.round.newfeat{dsidx,i}.assignedidx)) + tokeep=~ismember(ds.round.newfeat{dsidx,i}.assignedidx(:,6:7),discardifnew,'rows'); + dets{end+1}=ds.round.newfeat{dsidx,i}.assignedidx(tokeep,:); + feats{end+1}=double(ds.round.newfeat{dsidx,i}.feat(tokeep,:)); + if(isfield(ds.round.newfeat{dsidx,i},'ovlweights')) + allovlweight{end+1}=ds.round.newfeat{dsidx,i}.ovlweights(tokeep,:); + else + allovlweight{end+1}=ones(size(feats{end},1),1); + end + end + if(mod(i,100)==0) + disp(['img ' num2str(i) ' of ' num2str(numel(ds.round.myiminds))]) + end +end +ds.newdets{dsload('ds.round.roundid'),dsidx}=structcell2mat(dets(2:end)'); + +dets=structcell2mat(dets(:)); +allovlweight=structcell2mat(allovlweight(:)); +feats=structcell2mat(feats(:)); +if(size(feats,1)>500000) + error('featsall too big') +end + +% Distribute the detections in this batch by detector id, so we can train one +% detector at a time. +[dets feats allovlweight idforcell]=distributeby(dets, feats, allovlweight, dets(:,6)); +if(~all(idforcell==mydetrs(:))) + idforcell + mydetrs + error('something got out of order!'); +end + +% load the actual detectors. +ctrs=dsload(['ds.round.detectors{' num2str(dsidx) '}'],'clear'); +newctrs=zeros(size(ctrs)); +resfeat={}; +resdets={}; + +ds.round.newfeat={}; +nsv=[]; +for(i=1:numel(mydetrs)) + a=tic; + mymemory; + weights=allovlweight{i}; + + disp(['optimizing:' num2str(mydetrs(i))]); + disp(['total features:' num2str(size(feats{i},1))]); + + % Pull out the detector and optimize it. See doGradientDescentproj. + ctr=effstridx(ctrs,i); + [newctrtmp,scores]=ds.conf.params.graddescfun(feats{i}',imgs.label(dets{i}(:,7))==ds.classperbatch(dsidx),[ctr.w ctr.b]',weights,dsload('ds.round.roundid')); + newctr{i,1}=ctr; + newctrtmp=newctrtmp(:)'; + newctr{i}.w=newctrtmp(1:end-1); + newctr{i}.b=newctrtmp(end); + + dets{i}(:,5)=scores(:); + + % discard any detections that have low scores. We keep any detections + % with score higher than -.02/round_id, and keep at least as many detections + % as the number of dimensions divided by 5. Any less than this and the detections + % on the next round could cause the element to overfit to a tiny number of patches + % on the next round, and the round after that it will fire all over the place. + thr=sort(scores,'descend'); + thr=min(-.02/dsload('ds.round.ndetrounds'),thr(min(ceil(size(ctr.w,2)/5),numel(thr)))); + scores(1)=Inf;%make sure we keep the first one, since the rest of the code assumes it's there. + feats{i}=feats{i}((scores>=thr)',:); + dets{i}=dets{i}(scores>=thr,:); + allovlweight{i}=allovlweight{i}(scores>=thr); + nsv(i,1)=sum(scores>=thr); + toc(a) +end + +% convert the set of detections and features into giant matrices to save. +dets=cell2mat(dets(:)); +feats=cell2mat(feats(:)); +ds.nextround.prevdets{dsidx}=dets; +ds.nextround.nsv{dsidx}=nsv; +% again, if we have local storage, save it locally. Otherwise, save it to dswork. +if(dsfield(ds,'sys','distproc','localdir')) + prevfeats=feats; + save([ds.sys.distproc.localdir 'prevfeats' num2str(dsidx) '_' num2str(ds.round.roundid) '.mat'],'prevfeats'); +else + ds.nextround.prevfeats{dsidx}=resfeat; +end +ds.nextround.detectors{dsidx}=effstrcell2mat(newctr); + +dssave(); +ds.nextround=struct(); +ds.round=struct(); +ds.newdets={}; diff --git a/bestInImbb.m b/bestInImbb.m new file mode 100755 index 0000000..1e1fe22 --- /dev/null +++ b/bestInImbb.m @@ -0,0 +1,145 @@ +% The core of the detection code. Don't call this; call detectInIm instead. +% Note that this supports bounding boxes, but they're not used in the +% indoor67 code. +function [posall,distall,clustidall,featsall,flipall,boxidall]=bestInIm(centers,imid,conf) + global ds; + if(~exist('conf','var')) + conf=struct(); + end + conf=overrideConf(ds.conf.params,conf); + if(~dsfield(conf,'thresh')) + conf.thresh=-Inf; + end + imfull=im2double(getimg(imid)); + noprocess=0; + boxidall=[]; + flipall=[]; + if(dsfield(conf,'detsforclass'))% the flag indicating that we're using Pascal bounding boxes. + annot=getannot(imid); + bbs=[annot.x1 annot.y1 annot.x2 annot.y2]; + classes=[annot.label]; + occl=annot.occluded; + difficult=annot.difficult; + boxid=annot.boxid; + flip=zeros(size(bbs,1),1); + bbminsize=[bbs(:,4)-bbs(:,2)+1,bbs(:,3)-bbs(:,1)+1]; + if(dsbool(conf,'allowoccluded')) + occl(:)=false; + end + valid=(~occl & ismember(classes,conf.detsforclass) & ~difficult & all(bsxfun(@ge,bbminsize,ds.conf.params.patchCanonicalSize),2)); + bbs(~valid,:)=[]; + boxid(~valid)=[]; + flip(~valid)=[]; + else % in this case, we simply specify one bounding box containing the full image. + bbs=[1,1,size(imfull,2),size(imfull,1)]; + bbminsize=[bbs(:,4)-bbs(:,2)+1,bbs(:,3)-bbs(:,1)+1]; + if(~all(bbminsize>=ds.conf.params.patchCanonicalSize)) + bbs=[]; + end + boxid=0; + flip=0; + end + if(dsbool(conf,'flipall')) %add a flipped bounding box. + bbs=[bbs;bbs]; + boxid=[boxid;boxid]; + flip=[flip;ones(size(flip))]; + end + if(isempty(bbs)) + posall=[]; + distall=[]; + clustidall=[]; + featsall=[]; + boxid=[]; + return + end + for(bbidx=1:size(bbs,1)) + im=imfull(bbs(bbidx,2):bbs(bbidx,4),bbs(bbidx,1):bbs(bbidx,3),:); + if(flip(bbidx)),im=im(:,end:-1:1,:);end + pyramid = constructFeaturePyramid(im, ds.conf.params); % HOG feature pyramid + pcs=round(ds.conf.params.patchCanonicalSize/ds.conf.params.sBins)-2; + pcs(3)=size(pyramid.features{1},3); + pcs(4)=0; + conf.imid=imid; + % unentangling the fieature pyramid gives us 'features', where each row + % is the feature vector for a single patch. levels and indexes specify + % where those patches were in the pyramid, and gradsums tells us the strength + % of the gradient in each patch so we can get rid of empty ones that are likely + % to make our detectors misfire thanks to HOG's normalization. + [features, levels, indexes,gradsums] = unentanglePyramid(pyramid, ... + pcs,conf); + + + invalid=(gradsums<9); + features(invalid,:)=[]; + levels(invalid)=[]; + indexes(invalid,:)=[]; + gradsums(invalid)=[]; + + if(dsbool(conf,'multperim')) + % findmatches finds all detections above a certain threshold + [assignedidx, dist, clustid]=findmatches(centers,features,conf.thresh,conf); + else + % assigntoclosest gives us the top detection for each detector. + [assignedidx, dist]=assigntoclosest(centers,features,1); + if(isempty(dist)) + clustid=[]; + else + clustid=(1:size(centers,1))'; + valid=dist>conf.thresh; + assignedidx=assignedidx(valid); + dist=dist(valid); + clustid=clustid(valid); + end + end + patsz=ds.conf.params.patchCanonicalSize;%allsz(resinds(k),:); + fsz=(patsz-2*ds.conf.params.sBins)/ds.conf.params.sBins; + imgs=getimgs(); + % convert the pyramid indexes into bounding boxes with pixel coordinates. + pos=pyridx2pos(indexes(assignedidx,:),reshape(levels(assignedidx),[],1),fsz,pyramid); + % if we allow more than one per image, we need to do non-maximum suppresion. + if(dsbool(conf,'multperim')) + pos=[pos.x1 pos.y1 pos.x2 pos.y2]; + [pos,assignedidx,dist,clustidl,clustid]=distributeby(pos,assignedidx,dist,clustid,clustid); + for(i=1:numel(pos)) + [posinds]=myNms([pos{i} dist{i}],ds.conf.params.nmsOverlapThreshold); + assignedidx{i}=assignedidx{i}(posinds); + dist{i}=dist{i}(posinds); + pos{i}=pos{i}(posinds,:); + clustidl{i}=clustidl{i}(posinds); + end + assignedidx=cell2mat(assignedidx); + dist=cell2mat(dist); + p=cell2mat(pos); + if(isempty(p)) + p=zeros(0,4); + end + clear pos; + pos.x1=p(:,1);pos.x2=p(:,3);pos.y1=p(:,2);pos.y2=p(:,4); + clustid=cell2mat(clustidl); + end + feats=features(assignedidx,:); + pos.x1=pos.x1+bbs(bbidx,1)-1; + pos.x2=pos.x2+bbs(bbidx,1)-1; + pos.y1=pos.y1+bbs(bbidx,2)-1; + pos.y2=pos.y2+bbs(bbidx,2)-1; + if(flip(bbidx)) + medval=(bbs(bbidx,3)+bbs(bbidx,1))/2; + tmp=medval+(medval-pos.x2); + pos.x2=medval+(medval-pos.x1); + pos.x1=tmp; + end + + posall{bbidx,1}=effstr2str(pos); + featsall{bbidx,1}=feats; + distall{bbidx,1}=dist; + clustidall{bbidx,1}=clustid; + boxidall{bbidx,1}=repmat(boxid(bbidx),size(dist,1),1); + flipall{bbidx,1}=repmat(flip(bbidx),size(dist,1),1); + end + posall=str2effstr(cell2mat(posall)); + featsall=cell2mat(featsall); + distall=cell2mat(distall); + clustidall=cell2mat(clustidall); + boxidall=cell2mat(boxidall); + flipall=cell2mat(flipall); +end diff --git a/clipper/C#/ConsoleDemo/ConsoleDemo.sln b/clipper/C#/ConsoleDemo/ConsoleDemo.sln new file mode 100755 index 0000000..0ee75df --- /dev/null +++ b/clipper/C#/ConsoleDemo/ConsoleDemo.sln @@ -0,0 +1,45 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual C# Express 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleDemo", "ConsoleDemo\ConsoleDemo.csproj", "{185E6664-6A68-4377-99BE-4D4BFED19298}" + ProjectSection(ProjectDependencies) = postProject + {9B062971-A88E-4A3D-B3C9-12B78D15FA66} = {9B062971-A88E-4A3D-B3C9-12B78D15FA66} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "clipper_library", "..\clipper_library\clipper_library.csproj", "{9B062971-A88E-4A3D-B3C9-12B78D15FA66}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {185E6664-6A68-4377-99BE-4D4BFED19298}.Debug|Any CPU.ActiveCfg = Debug|x86 + {185E6664-6A68-4377-99BE-4D4BFED19298}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {185E6664-6A68-4377-99BE-4D4BFED19298}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {185E6664-6A68-4377-99BE-4D4BFED19298}.Debug|x86.ActiveCfg = Debug|x86 + {185E6664-6A68-4377-99BE-4D4BFED19298}.Debug|x86.Build.0 = Debug|x86 + {185E6664-6A68-4377-99BE-4D4BFED19298}.Release|Any CPU.ActiveCfg = Release|x86 + {185E6664-6A68-4377-99BE-4D4BFED19298}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {185E6664-6A68-4377-99BE-4D4BFED19298}.Release|Mixed Platforms.Build.0 = Release|x86 + {185E6664-6A68-4377-99BE-4D4BFED19298}.Release|x86.ActiveCfg = Release|x86 + {185E6664-6A68-4377-99BE-4D4BFED19298}.Release|x86.Build.0 = Release|x86 + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|x86.ActiveCfg = Debug|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Any CPU.Build.0 = Release|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|x86.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/clipper/C#/ConsoleDemo/ConsoleDemo/ConsoleDemo.csproj b/clipper/C#/ConsoleDemo/ConsoleDemo/ConsoleDemo.csproj new file mode 100755 index 0000000..ab09c30 --- /dev/null +++ b/clipper/C#/ConsoleDemo/ConsoleDemo/ConsoleDemo.csproj @@ -0,0 +1,103 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {185E6664-6A68-4377-99BE-4D4BFED19298} + Exe + Properties + ConsoleDemo + ConsoleDemo + v4.0 + Client + 512 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + + + + + + + + + + + + + + + False + Microsoft .NET Framework 4 Client Profile %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + {9B062971-A88E-4A3D-B3C9-12B78D15FA66} + clipper_library + + + + + \ No newline at end of file diff --git a/clipper/C#/ConsoleDemo/ConsoleDemo/Program.cs b/clipper/C#/ConsoleDemo/ConsoleDemo/Program.cs new file mode 100755 index 0000000..344414d --- /dev/null +++ b/clipper/C#/ConsoleDemo/ConsoleDemo/Program.cs @@ -0,0 +1,453 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Globalization; +using ClipperLib; + +namespace ClipperTest1 +{ + using Polygon = List; + using Polygons = List>; + + class Program + { + + //a very simple class that builds an SVG file with any number of + //polygons of the specified formats ... + class SVGBuilder + { + + public class StyleInfo + { + public PolyFillType pft; + public Color brushClr; + public Color penClr; + public double penWidth; + public int[] dashArray; + public Boolean showCoords; + public StyleInfo Clone() + { + StyleInfo si = new StyleInfo(); + si.pft = this.pft; + si.brushClr = this.brushClr; + si.dashArray = this.dashArray; + si.penClr = this.penClr; + si.penWidth = this.penWidth; + si.showCoords = this.showCoords; + return si; + } + public StyleInfo() + { + pft = PolyFillType.pftNonZero; + brushClr = Color.AntiqueWhite; + dashArray = null; + penClr = Color.Black; + penWidth = 0.8; + showCoords = false; + } + } + + public class PolyInfo + { + public Polygons polygons; + public StyleInfo si; + } + + public StyleInfo style; + private List PolyInfoList; + const string svg_header = "\n" + + "\n\n" + + "\n\n"; + const string svg_path_format = "\"\n style=\"fill:{0};" + + " fill-opacity:{1:f2}; fill-rule:{2}; stroke:{3};" + + " stroke-opacity:{4:f2}; stroke-width:{5:f2};\"/>\n\n"; + + public SVGBuilder() + { + PolyInfoList = new List(); + style = new StyleInfo(); + } + + public void AddPolygons(Polygons poly) + { + if (poly.Count == 0) return; + PolyInfo pi = new PolyInfo(); + pi.polygons = poly; + pi.si = style.Clone(); + PolyInfoList.Add(pi); + } + + public Boolean SaveToFile(string filename, double scale = 1.0, int margin = 10) + { + if (scale == 0) scale = 1.0; + if (margin < 0) margin = 0; + + //calculate the bounding rect ... + int i = 0, j = 0; + while (i < PolyInfoList.Count) + { + j = 0; + while (j < PolyInfoList[i].polygons.Count && + PolyInfoList[i].polygons[j].Count == 0) j++; + if (j < PolyInfoList[i].polygons.Count) break; + i++; + } + if (i == PolyInfoList.Count) return false; + IntRect rec = new IntRect(); + rec.left = PolyInfoList[i].polygons[j][0].X; + rec.right = rec.left; + rec.top = PolyInfoList[0].polygons[j][0].Y; + rec.bottom = rec.top; + + for ( ; i < PolyInfoList.Count; i++ ) + { + foreach (Polygon pg in PolyInfoList[i].polygons) + foreach (IntPoint pt in pg) + { + if (pt.X < rec.left) rec.left = pt.X; + else if (pt.X > rec.right) rec.right = pt.X; + if (pt.Y < rec.top) rec.top = pt.Y; + else if (pt.Y > rec.bottom) rec.bottom = pt.Y; + } + } + + rec.left = (Int64)((double)rec.left * scale); + rec.top = (Int64)((double)rec.top * scale); + rec.right = (Int64)((double)rec.right * scale); + rec.bottom = (Int64)((double)rec.bottom * scale); + Int64 offsetX = -rec.left + margin; + Int64 offsetY = -rec.top + margin; + + StreamWriter writer = new StreamWriter(filename); + if (writer == null) return false; + writer.Write(svg_header, + (rec.right - rec.left) + margin * 2, + (rec.bottom - rec.top) + margin * 2, + (rec.right - rec.left) + margin * 2, + (rec.bottom - rec.top) + margin * 2); + + foreach (PolyInfo pi in PolyInfoList) + { + writer.Write(" \n\n"); + foreach (Polygon p in pi.polygons) + { + foreach (IntPoint pt in p) + { + Int64 x = pt.X; + Int64 y = pt.Y; + writer.Write(String.Format( + "{2},{3}\n", + (int)(x * scale + offsetX), (int)(y * scale + offsetY), x, y)); + + } + writer.Write("\n"); + } + writer.Write("\n"); + } + } + writer.Write("\n"); + writer.Close(); + return true; + } + } + + //////////////////////////////////////////////// + + static bool LoadFromFile(string filename, Polygons ppg, int dec_places, int xOffset = 0, int yOffset = 0) + { + double scaling; + scaling = Math.Pow(10, dec_places); + + ppg.Clear(); + if (!File.Exists(filename)) return false; + StreamReader sr = new StreamReader(filename); + if (sr == null) return false; + string line; + if ((line = sr.ReadLine()) == null) return false; + int polyCnt, vertCnt; + if (!Int32.TryParse(line, out polyCnt) || polyCnt < 0) return false; + ppg.Capacity = polyCnt; + for (int i = 0; i < polyCnt; i++) + { + if ((line = sr.ReadLine()) == null) return false; + if (!Int32.TryParse(line, out vertCnt) || vertCnt < 0) return false; + Polygon pg = new Polygon(vertCnt); + ppg.Add(pg); + if (scaling > 0.999 & scaling < 1.001) + for (int j = 0; j < vertCnt; j++) + { + Int64 x, y; + if ((line = sr.ReadLine()) == null) return false; + char[] delimiters = new char[] { ',', ' ' }; + string[] vals = line.Split(delimiters); + if (vals.Length < 2) return false; + if (!Int64.TryParse(vals[0], out x)) return false; + if (!Int64.TryParse(vals[1], out y)) + if (vals.Length < 2 || !Int64.TryParse(vals[2], out y)) return false; + x = x + xOffset; + y = y + yOffset; + pg.Add(new IntPoint(x, y)); + } + else + for (int j = 0; j < vertCnt; j++) + { + double x, y; + if ((line = sr.ReadLine()) == null) return false; + char[] delimiters = new char[] { ',', ' ' }; + string[] vals = line.Split(delimiters); + if (vals.Length < 2) return false; + if (!double.TryParse(vals[0], out x)) return false; + if (!double.TryParse(vals[1], out y)) + if (vals.Length < 2 || !double.TryParse(vals[2], out y)) return false; + x = x * scaling + xOffset; + y = y * scaling + yOffset; + pg.Add(new IntPoint((Int64)Math.Round(x), (Int64)Math.Round(y))); + } + } + return true; + } + + //////////////////////////////////////////////// + static void SaveToFile(string filename, Polygons ppg, int dec_places) + { + double scaling = Math.Pow(10, dec_places); + StreamWriter writer = new StreamWriter(filename); + if (writer == null) return; + writer.Write("{0}\r\n", ppg.Count); + foreach (Polygon pg in ppg) + { + writer.Write("{0}\r\n", pg.Count); + foreach (IntPoint ip in pg) + writer.Write("{0:0.####}, {1:0.####}\r\n", (double)ip.X / scaling, (double)ip.Y / scaling); + } + writer.Close(); + } + + //////////////////////////////////////////////// + + static void OutputFileFormat() + { + Console.WriteLine("The expected (text) file format is ..."); + Console.WriteLine("Polygon Count"); + Console.WriteLine("First polygon vertex count"); + Console.WriteLine("first X, Y coordinate of first polygon"); + Console.WriteLine("second X, Y coordinate of first polygon"); + Console.WriteLine("etc."); + Console.WriteLine("Second polygon vertex count (if there is one)"); + Console.WriteLine("first X, Y coordinate of second polygon"); + Console.WriteLine("second X, Y coordinate of second polygon"); + Console.WriteLine("etc."); + } + + //////////////////////////////////////////////// + + static Polygon IntsToPolygon(int[] ints) + { + int len1 = ints.Length /2; + Polygon result = new Polygon(len1); + for (int i = 0; i < len1; i++) + result.Add(new IntPoint(ints[i * 2], ints[i * 2 +1])); + return result; + } + + //////////////////////////////////////////////// + + static Polygon MakeRandomPolygon(Random r, int maxWidth, int maxHeight, int edgeCount, Int64 scale = 1) + { + Polygon result = new Polygon(edgeCount); + for (int i = 0; i < edgeCount; i++) + { + result.Add(new IntPoint(r.Next(maxWidth)*scale, r.Next(maxHeight)*scale)); + } + return result; + } + //////////////////////////////////////////////// + + static void Main(string[] args) + { + ////quick test with random polygons ... + //Polygons ss = new Polygons(1), cc = new Polygons(1), sss = new Polygons(); + //Random r = new Random((int)DateTime.Now.Ticks); + //int scale = 1000000000; //tests 128bit math + //ss.Add(MakeRandomPolygon(r, 400, 350, 9, scale)); + //cc.Add(MakeRandomPolygon(r, 400, 350, 9, scale)); + //Clipper cpr = new Clipper(); + //cpr.AddPolygons(ss, PolyType.ptSubject); + //cpr.AddPolygons(cc, PolyType.ptClip); + //cpr.Execute(ClipType.ctUnion, sss, PolyFillType.pftNonZero, PolyFillType.pftNonZero); + //sss = Clipper.OffsetPolygons(sss, -5.0*scale, JoinType.jtMiter, 4); + //SVGBuilder svg1 = new SVGBuilder(); + //svg1.style.brushClr = Color.FromArgb(0x20, 0, 0, 0x9c); + //svg1.style.penClr = Color.FromArgb(0xd3, 0xd3, 0xda); + //svg1.AddPolygons(ss); + //svg1.style.brushClr = Color.FromArgb(0x20, 0x9c, 0, 0); + //svg1.style.penClr = Color.FromArgb(0xff, 0xa0, 0x7a); + //svg1.AddPolygons(cc); + //svg1.style.brushClr = Color.FromArgb(0xAA, 0x80, 0xff, 0x9c); + //svg1.style.penClr = Color.FromArgb(0, 0x33, 0); + //svg1.AddPolygons(sss); + //svg1.SaveToFile("solution.svg", 1.0/scale); + //return; + + if (args.Length < 5) + { + string appname = System.Environment.GetCommandLineArgs()[0]; + appname = Path.GetFileName(appname); + Console.WriteLine(""); + Console.WriteLine("Usage:"); + Console.WriteLine(" {0} CLIPTYPE s_file c_file INPUT_DEC_PLACES SVG_SCALE [S_FILL, C_FILL]", appname); + Console.WriteLine(" where ..."); + Console.WriteLine(" CLIPTYPE = INTERSECTION|UNION|DIFFERENCE|XOR"); + Console.WriteLine(" FILLMODE = NONZERO|EVENODD"); + Console.WriteLine(" INPUT_DEC_PLACES = signific. decimal places for subject & clip coords."); + Console.WriteLine(" SVG_SCALE = scale of SVG image as power of 10. (Fractions are accepted.)"); + Console.WriteLine(" both S_FILL and C_FILL are optional. The default is EVENODD."); + Console.WriteLine("Example:"); + Console.WriteLine(" Intersect polygons, rnd to 4 dec places, SVG is 1/100 normal size ..."); + Console.WriteLine(" {0} INTERSECTION subj.txt clip.txt 0 0 NONZERO NONZERO", appname); + return; + } + + ClipType ct; + switch (args[0].ToUpper()) + { + case "INTERSECTION": ct = ClipType.ctIntersection; break; + case "UNION": ct = ClipType.ctUnion; break; + case "DIFFERENCE": ct = ClipType.ctDifference; break; + case "XOR": ct = ClipType.ctXor; break; + default: Console.WriteLine("Error: invalid operation - {0}", args[0]); return; + } + + string subjFilename = args[1]; + string clipFilename = args[2]; + if (!File.Exists(subjFilename)) + { + Console.WriteLine("Error: file - {0} - does not exist.", subjFilename); + return; + } + if (!File.Exists(clipFilename)) + { + Console.WriteLine("Error: file - {0} - does not exist.", clipFilename); + return; + } + + int decimal_places = 0; + if (!Int32.TryParse(args[3], out decimal_places)) + { + Console.WriteLine("Error: invalid number of decimal places - {0}", args[3]); + return; + } + if (decimal_places > 8) decimal_places = 8; + else if (decimal_places < 0) decimal_places = 0; + + double svg_scale = 0; + if (!double.TryParse(args[4], out svg_scale)) + { + Console.WriteLine("Error: invalid value for SVG_SCALE - {0}", args[4]); + return; + } + if (svg_scale < -18) svg_scale = -18; + else if (svg_scale > 18) svg_scale = 18; + svg_scale = Math.Pow(10, svg_scale - decimal_places);//nb: also compensate for decimal places + + + PolyFillType pftSubj = PolyFillType.pftEvenOdd; + PolyFillType pftClip = PolyFillType.pftEvenOdd; + if (args.Length > 6) + { + switch (args[5].ToUpper()) + { + case "EVENODD": pftSubj = PolyFillType.pftEvenOdd; break; + case "NONZERO": pftSubj = PolyFillType.pftNonZero; break; + default: Console.WriteLine("Error: invalid cliptype - {0}", args[5]); return; + } + switch (args[6].ToUpper()) + { + case "EVENODD": pftClip = PolyFillType.pftEvenOdd; break; + case "NONZERO": pftClip = PolyFillType.pftNonZero; break; + default: Console.WriteLine("Error: invalid cliptype - {0}", args[6]); return; + } + } + + Polygons subjs = new Polygons(); + Polygons clips = new Polygons(); + if (!LoadFromFile(subjFilename, subjs, decimal_places)) + { + Console.WriteLine("Error processing subject polygons file - {0} ", subjFilename); + OutputFileFormat(); + return; + } + if (!LoadFromFile(clipFilename, clips, decimal_places)) + { + Console.WriteLine("Error processing clip polygons file - {0} ", clipFilename); + OutputFileFormat(); + return; + } + + Console.WriteLine("wait ..."); + Clipper cp = new Clipper(); + cp.AddPolygons(subjs, PolyType.ptSubject); + cp.AddPolygons(clips, PolyType.ptClip); + + Polygons solution = new Polygons(); + //Polygons solution = new Polygons(); + if (cp.Execute(ct, solution, pftSubj, pftClip)) + { + SaveToFile("solution.txt", solution, decimal_places); + + //solution = Clipper.OffsetPolygons(solution, -4, JoinType.jtRound); + + SVGBuilder svg = new SVGBuilder(); + svg.style.brushClr = Color.FromArgb(0x20, 0, 0, 0x9c); + svg.style.penClr = Color.FromArgb(0xd3, 0xd3, 0xda); + svg.AddPolygons(subjs); + svg.style.brushClr = Color.FromArgb(0x20, 0x9c, 0, 0); + svg.style.penClr = Color.FromArgb(0xff, 0xa0, 0x7a); + svg.AddPolygons(clips); + svg.style.brushClr = Color.FromArgb(0xAA, 0x80, 0xff, 0x9c); + svg.style.penClr = Color.FromArgb(0, 0x33, 0); + svg.AddPolygons(solution); + svg.SaveToFile("solution.svg", svg_scale); + + Console.WriteLine("finished!"); + } + else + { + Console.WriteLine("failed!"); + } + } + + } //class Program +} diff --git a/clipper/C#/ConsoleDemo/ConsoleDemo/Properties/AssemblyInfo.cs b/clipper/C#/ConsoleDemo/ConsoleDemo/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..5078525 --- /dev/null +++ b/clipper/C#/ConsoleDemo/ConsoleDemo/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ClipperTest1")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("ClipperTest1")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("a8009709-1ebc-44a6-a094-298ec0b23d42")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/clipper/C#/ConsoleDemo/ConsoleDemo/bin/Release/ConsoleDemo.exe b/clipper/C#/ConsoleDemo/ConsoleDemo/bin/Release/ConsoleDemo.exe new file mode 100755 index 0000000..2fb60e1 Binary files /dev/null and b/clipper/C#/ConsoleDemo/ConsoleDemo/bin/Release/ConsoleDemo.exe differ diff --git a/clipper/C#/ConsoleDemo/ConsoleDemo/bin/Release/clip.txt b/clipper/C#/ConsoleDemo/ConsoleDemo/bin/Release/clip.txt new file mode 100755 index 0000000..6a2a2b0 --- /dev/null +++ b/clipper/C#/ConsoleDemo/ConsoleDemo/bin/Release/clip.txt @@ -0,0 +1,6 @@ +1 +4 +120, 10, +240, 10, +240, 90, +120, 90 \ No newline at end of file diff --git a/clipper/C#/ConsoleDemo/ConsoleDemo/bin/Release/clipper_library.dll b/clipper/C#/ConsoleDemo/ConsoleDemo/bin/Release/clipper_library.dll new file mode 100755 index 0000000..18887a4 Binary files /dev/null and b/clipper/C#/ConsoleDemo/ConsoleDemo/bin/Release/clipper_library.dll differ diff --git a/clipper/C#/ConsoleDemo/ConsoleDemo/bin/Release/subj.txt b/clipper/C#/ConsoleDemo/ConsoleDemo/bin/Release/subj.txt new file mode 100755 index 0000000..7033a4c --- /dev/null +++ b/clipper/C#/ConsoleDemo/ConsoleDemo/bin/Release/subj.txt @@ -0,0 +1,13 @@ +1 +11 +0, 0, +200, 0, +200, 100, +50, 100, +50, 50, +75, 75, +100, 50, +75, 25, +50, 50, +50, 100, +0, 100, diff --git a/clipper/C#/GuiDemo/GuiDemo.sln b/clipper/C#/GuiDemo/GuiDemo.sln new file mode 100755 index 0000000..d17656f --- /dev/null +++ b/clipper/C#/GuiDemo/GuiDemo.sln @@ -0,0 +1,45 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual C# Express 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GuiDemo", "GuiDemo\GuiDemo.csproj", "{8BD44147-3290-4A73-BAA2-1C171566BC25}" + ProjectSection(ProjectDependencies) = postProject + {9B062971-A88E-4A3D-B3C9-12B78D15FA66} = {9B062971-A88E-4A3D-B3C9-12B78D15FA66} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "clipper_library", "..\clipper_library\clipper_library.csproj", "{9B062971-A88E-4A3D-B3C9-12B78D15FA66}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8BD44147-3290-4A73-BAA2-1C171566BC25}.Debug|Any CPU.ActiveCfg = Debug|x86 + {8BD44147-3290-4A73-BAA2-1C171566BC25}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {8BD44147-3290-4A73-BAA2-1C171566BC25}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {8BD44147-3290-4A73-BAA2-1C171566BC25}.Debug|x86.ActiveCfg = Debug|x86 + {8BD44147-3290-4A73-BAA2-1C171566BC25}.Debug|x86.Build.0 = Debug|x86 + {8BD44147-3290-4A73-BAA2-1C171566BC25}.Release|Any CPU.ActiveCfg = Release|x86 + {8BD44147-3290-4A73-BAA2-1C171566BC25}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {8BD44147-3290-4A73-BAA2-1C171566BC25}.Release|Mixed Platforms.Build.0 = Release|x86 + {8BD44147-3290-4A73-BAA2-1C171566BC25}.Release|x86.ActiveCfg = Release|x86 + {8BD44147-3290-4A73-BAA2-1C171566BC25}.Release|x86.Build.0 = Release|x86 + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|x86.ActiveCfg = Debug|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Any CPU.Build.0 = Release|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|x86.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/clipper/C#/GuiDemo/GuiDemo/Form1.Designer.cs b/clipper/C#/GuiDemo/GuiDemo/Form1.Designer.cs new file mode 100755 index 0000000..79e0846 --- /dev/null +++ b/clipper/C#/GuiDemo/GuiDemo/Form1.Designer.cs @@ -0,0 +1,419 @@ +namespace WindowsFormsApplication1 +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1)); + this.statusStrip1 = new System.Windows.Forms.StatusStrip(); + this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); + this.panel1 = new System.Windows.Forms.Panel(); + this.bSave = new System.Windows.Forms.Button(); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.rbNone = new System.Windows.Forms.RadioButton(); + this.rbXor = new System.Windows.Forms.RadioButton(); + this.rbDifference = new System.Windows.Forms.RadioButton(); + this.rbUnion = new System.Windows.Forms.RadioButton(); + this.rbIntersect = new System.Windows.Forms.RadioButton(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.rbTest2 = new System.Windows.Forms.RadioButton(); + this.rbTest1 = new System.Windows.Forms.RadioButton(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.label2 = new System.Windows.Forms.Label(); + this.nudOffset = new System.Windows.Forms.NumericUpDown(); + this.lblCount = new System.Windows.Forms.Label(); + this.nudCount = new System.Windows.Forms.NumericUpDown(); + this.rbNonZero = new System.Windows.Forms.RadioButton(); + this.rbEvenOdd = new System.Windows.Forms.RadioButton(); + this.bRefresh = new System.Windows.Forms.Button(); + this.bCancel = new System.Windows.Forms.Button(); + this.panel2 = new System.Windows.Forms.Panel(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.saveFileDialog1 = new System.Windows.Forms.SaveFileDialog(); + this.statusStrip1.SuspendLayout(); + this.panel1.SuspendLayout(); + this.groupBox3.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.groupBox1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.nudOffset)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudCount)).BeginInit(); + this.panel2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + this.SuspendLayout(); + // + // statusStrip1 + // + this.statusStrip1.GripStyle = System.Windows.Forms.ToolStripGripStyle.Visible; + this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.toolStripStatusLabel1}); + this.statusStrip1.Location = new System.Drawing.Point(0, 459); + this.statusStrip1.Name = "statusStrip1"; + this.statusStrip1.Size = new System.Drawing.Size(716, 22); + this.statusStrip1.TabIndex = 4; + // + // toolStripStatusLabel1 + // + this.toolStripStatusLabel1.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + this.toolStripStatusLabel1.Name = "toolStripStatusLabel1"; + this.toolStripStatusLabel1.Size = new System.Drawing.Size(0, 17); + // + // panel1 + // + this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.panel1.Controls.Add(this.bSave); + this.panel1.Controls.Add(this.groupBox3); + this.panel1.Controls.Add(this.groupBox2); + this.panel1.Controls.Add(this.groupBox1); + this.panel1.Controls.Add(this.bRefresh); + this.panel1.Controls.Add(this.bCancel); + this.panel1.Dock = System.Windows.Forms.DockStyle.Left; + this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(121, 459); + this.panel1.TabIndex = 5; + // + // bSave + // + this.bSave.Location = new System.Drawing.Point(9, 412); + this.bSave.Name = "bSave"; + this.bSave.Size = new System.Drawing.Size(100, 25); + this.bSave.TabIndex = 9; + this.bSave.Text = "S&ave as SVG File"; + this.bSave.UseVisualStyleBackColor = true; + this.bSave.Click += new System.EventHandler(this.bSave_Click); + // + // groupBox3 + // + this.groupBox3.Controls.Add(this.rbNone); + this.groupBox3.Controls.Add(this.rbXor); + this.groupBox3.Controls.Add(this.rbDifference); + this.groupBox3.Controls.Add(this.rbUnion); + this.groupBox3.Controls.Add(this.rbIntersect); + this.groupBox3.Location = new System.Drawing.Point(9, 12); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.Size = new System.Drawing.Size(100, 125); + this.groupBox3.TabIndex = 5; + this.groupBox3.TabStop = false; + this.groupBox3.Text = "&Boolean Op:"; + // + // rbNone + // + this.rbNone.AutoSize = true; + this.rbNone.Location = new System.Drawing.Point(14, 100); + this.rbNone.Name = "rbNone"; + this.rbNone.Size = new System.Drawing.Size(51, 17); + this.rbNone.TabIndex = 4; + this.rbNone.Text = "None"; + this.rbNone.UseVisualStyleBackColor = true; + this.rbNone.CheckedChanged += new System.EventHandler(this.rbNonZero_Click); + // + // rbXor + // + this.rbXor.AutoSize = true; + this.rbXor.Location = new System.Drawing.Point(14, 81); + this.rbXor.Name = "rbXor"; + this.rbXor.Size = new System.Drawing.Size(48, 17); + this.rbXor.TabIndex = 3; + this.rbXor.Text = "XOR"; + this.rbXor.UseVisualStyleBackColor = true; + this.rbXor.CheckedChanged += new System.EventHandler(this.rbNonZero_Click); + // + // rbDifference + // + this.rbDifference.AutoSize = true; + this.rbDifference.Location = new System.Drawing.Point(14, 60); + this.rbDifference.Name = "rbDifference"; + this.rbDifference.Size = new System.Drawing.Size(74, 17); + this.rbDifference.TabIndex = 2; + this.rbDifference.Text = "Difference"; + this.rbDifference.UseVisualStyleBackColor = true; + this.rbDifference.CheckedChanged += new System.EventHandler(this.rbNonZero_Click); + // + // rbUnion + // + this.rbUnion.AutoSize = true; + this.rbUnion.Location = new System.Drawing.Point(14, 39); + this.rbUnion.Name = "rbUnion"; + this.rbUnion.Size = new System.Drawing.Size(53, 17); + this.rbUnion.TabIndex = 1; + this.rbUnion.Text = "Union"; + this.rbUnion.UseVisualStyleBackColor = true; + this.rbUnion.CheckedChanged += new System.EventHandler(this.rbNonZero_Click); + // + // rbIntersect + // + this.rbIntersect.AutoSize = true; + this.rbIntersect.Checked = true; + this.rbIntersect.Location = new System.Drawing.Point(14, 19); + this.rbIntersect.Name = "rbIntersect"; + this.rbIntersect.Size = new System.Drawing.Size(66, 17); + this.rbIntersect.TabIndex = 0; + this.rbIntersect.TabStop = true; + this.rbIntersect.Text = "Intersect"; + this.rbIntersect.UseVisualStyleBackColor = true; + this.rbIntersect.CheckedChanged += new System.EventHandler(this.rbNonZero_Click); + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.rbTest2); + this.groupBox2.Controls.Add(this.rbTest1); + this.groupBox2.Location = new System.Drawing.Point(9, 310); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(100, 61); + this.groupBox2.TabIndex = 7; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Sample"; + // + // rbTest2 + // + this.rbTest2.AutoSize = true; + this.rbTest2.Location = new System.Drawing.Point(14, 35); + this.rbTest2.Name = "rbTest2"; + this.rbTest2.Size = new System.Drawing.Size(46, 17); + this.rbTest2.TabIndex = 1; + this.rbTest2.Text = "&Two"; + this.rbTest2.UseVisualStyleBackColor = true; + this.rbTest2.Click += new System.EventHandler(this.rbTest1_Click); + // + // rbTest1 + // + this.rbTest1.AutoSize = true; + this.rbTest1.Checked = true; + this.rbTest1.Location = new System.Drawing.Point(14, 17); + this.rbTest1.Name = "rbTest1"; + this.rbTest1.Size = new System.Drawing.Size(45, 17); + this.rbTest1.TabIndex = 0; + this.rbTest1.TabStop = true; + this.rbTest1.Text = "&One"; + this.rbTest1.UseVisualStyleBackColor = true; + this.rbTest1.Click += new System.EventHandler(this.rbTest1_Click); + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.label2); + this.groupBox1.Controls.Add(this.nudOffset); + this.groupBox1.Controls.Add(this.lblCount); + this.groupBox1.Controls.Add(this.nudCount); + this.groupBox1.Controls.Add(this.rbNonZero); + this.groupBox1.Controls.Add(this.rbEvenOdd); + this.groupBox1.Location = new System.Drawing.Point(9, 144); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(100, 159); + this.groupBox1.TabIndex = 6; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Options:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(11, 108); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(38, 13); + this.label2.TabIndex = 4; + this.label2.Text = "O&ffset:"; + // + // nudOffset + // + this.nudOffset.DecimalPlaces = 1; + this.nudOffset.Location = new System.Drawing.Point(14, 126); + this.nudOffset.Maximum = new decimal(new int[] { + 10, + 0, + 0, + 0}); + this.nudOffset.Minimum = new decimal(new int[] { + 10, + 0, + 0, + -2147483648}); + this.nudOffset.Name = "nudOffset"; + this.nudOffset.Size = new System.Drawing.Size(54, 20); + this.nudOffset.TabIndex = 5; + this.nudOffset.ValueChanged += new System.EventHandler(this.nudCount_ValueChanged); + // + // lblCount + // + this.lblCount.AutoSize = true; + this.lblCount.Location = new System.Drawing.Point(11, 62); + this.lblCount.Name = "lblCount"; + this.lblCount.Size = new System.Drawing.Size(71, 13); + this.lblCount.TabIndex = 2; + this.lblCount.Text = "Vertex &Count:"; + // + // nudCount + // + this.nudCount.Location = new System.Drawing.Point(14, 80); + this.nudCount.Minimum = new decimal(new int[] { + 3, + 0, + 0, + 0}); + this.nudCount.Name = "nudCount"; + this.nudCount.Size = new System.Drawing.Size(54, 20); + this.nudCount.TabIndex = 3; + this.nudCount.Value = new decimal(new int[] { + 50, + 0, + 0, + 0}); + this.nudCount.ValueChanged += new System.EventHandler(this.bRefresh_Click); + // + // rbNonZero + // + this.rbNonZero.AutoSize = true; + this.rbNonZero.Checked = true; + this.rbNonZero.Location = new System.Drawing.Point(14, 39); + this.rbNonZero.Name = "rbNonZero"; + this.rbNonZero.Size = new System.Drawing.Size(67, 17); + this.rbNonZero.TabIndex = 1; + this.rbNonZero.TabStop = true; + this.rbNonZero.Text = "Non&Zero"; + this.rbNonZero.UseVisualStyleBackColor = true; + this.rbNonZero.Click += new System.EventHandler(this.rbNonZero_Click); + // + // rbEvenOdd + // + this.rbEvenOdd.AutoSize = true; + this.rbEvenOdd.Location = new System.Drawing.Point(14, 21); + this.rbEvenOdd.Name = "rbEvenOdd"; + this.rbEvenOdd.Size = new System.Drawing.Size(70, 17); + this.rbEvenOdd.TabIndex = 0; + this.rbEvenOdd.Text = "&EvenOdd"; + this.rbEvenOdd.UseVisualStyleBackColor = true; + this.rbEvenOdd.Click += new System.EventHandler(this.rbNonZero_Click); + // + // bRefresh + // + this.bRefresh.Location = new System.Drawing.Point(9, 381); + this.bRefresh.Name = "bRefresh"; + this.bRefresh.Size = new System.Drawing.Size(100, 25); + this.bRefresh.TabIndex = 8; + this.bRefresh.Text = "&New Sample"; + this.bRefresh.UseVisualStyleBackColor = true; + this.bRefresh.Click += new System.EventHandler(this.bRefresh_Click); + // + // bCancel + // + this.bCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.bCancel.Location = new System.Drawing.Point(9, 458); + this.bCancel.Name = "bCancel"; + this.bCancel.Size = new System.Drawing.Size(100, 27); + this.bCancel.TabIndex = 11; + this.bCancel.Text = "E&xit"; + this.bCancel.UseVisualStyleBackColor = true; + this.bCancel.Click += new System.EventHandler(this.bClose_Click); + // + // panel2 + // + this.panel2.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.panel2.Controls.Add(this.pictureBox1); + this.panel2.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel2.Location = new System.Drawing.Point(121, 0); + this.panel2.Name = "panel2"; + this.panel2.Size = new System.Drawing.Size(595, 459); + this.panel2.TabIndex = 6; + // + // pictureBox1 + // + this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBox1.Location = new System.Drawing.Point(0, 0); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(591, 455); + this.pictureBox1.TabIndex = 1; + this.pictureBox1.TabStop = false; + this.pictureBox1.DoubleClick += new System.EventHandler(this.bRefresh_Click); + // + // saveFileDialog1 + // + this.saveFileDialog1.DefaultExt = "svg"; + this.saveFileDialog1.Filter = "SVG Files (*.svg)|*.svg"; + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(716, 481); + this.Controls.Add(this.panel2); + this.Controls.Add(this.panel1); + this.Controls.Add(this.statusStrip1); + this.DoubleBuffered = true; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.KeyPreview = true; + this.Name = "Form1"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Clipper C# Demo1"; + this.Load += new System.EventHandler(this.Form1_Load); + this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Form1_KeyDown); + this.Resize += new System.EventHandler(this.Form1_Resize); + this.statusStrip1.ResumeLayout(false); + this.statusStrip1.PerformLayout(); + this.panel1.ResumeLayout(false); + this.groupBox3.ResumeLayout(false); + this.groupBox3.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.nudOffset)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudCount)).EndInit(); + this.panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.StatusStrip statusStrip1; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.RadioButton rbNone; + private System.Windows.Forms.RadioButton rbXor; + private System.Windows.Forms.RadioButton rbDifference; + private System.Windows.Forms.RadioButton rbUnion; + private System.Windows.Forms.RadioButton rbIntersect; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.RadioButton rbTest2; + private System.Windows.Forms.RadioButton rbTest1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.NumericUpDown nudOffset; + private System.Windows.Forms.Label lblCount; + private System.Windows.Forms.NumericUpDown nudCount; + private System.Windows.Forms.RadioButton rbNonZero; + private System.Windows.Forms.RadioButton rbEvenOdd; + private System.Windows.Forms.Button bRefresh; + private System.Windows.Forms.Button bCancel; + private System.Windows.Forms.Panel panel2; + private System.Windows.Forms.PictureBox pictureBox1; + private System.Windows.Forms.Button bSave; + private System.Windows.Forms.SaveFileDialog saveFileDialog1; + } +} + diff --git a/clipper/C#/GuiDemo/GuiDemo/Form1.cs b/clipper/C#/GuiDemo/GuiDemo/Form1.cs new file mode 100755 index 0000000..b8e5984 --- /dev/null +++ b/clipper/C#/GuiDemo/GuiDemo/Form1.cs @@ -0,0 +1,661 @@ +using System; +using System.Diagnostics; +using System.Text; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Reflection; +using System.Linq; +using System.Windows.Forms; +using System.Globalization; +using ClipperLib; + + +namespace WindowsFormsApplication1 +{ + + using Polygon = List; + using Polygons = List>; + + public partial class Form1 : Form + { + + Assembly _assembly; + Stream polyStream; + + private Bitmap mybitmap; + private Polygons subjects; + private Polygons clips; + private Polygons solution; + + //Here we are scaling all coordinates up by 100 when they're passed to Clipper + //via Polygon (or Polygons) objects because Clipper no longer accepts floating + //point values. Likewise when Clipper returns a solution in a Polygons object, + //we need to scale down these returned values by the same amount before displaying. + private int scale = 100; //or 1 or 10 or 10000 etc for lesser or greater precision. + + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + + //a very simple class that builds an SVG file with any number of + //polygons of the specified formats ... + class SVGBuilder + { + + public class StyleInfo + { + public PolyFillType pft; + public Color brushClr; + public Color penClr; + public double penWidth; + public int[] dashArray; + public Boolean showCoords; + public StyleInfo Clone() + { + StyleInfo si = new StyleInfo(); + si.pft = this.pft; + si.brushClr = this.brushClr; + si.dashArray = this.dashArray; + si.penClr = this.penClr; + si.penWidth = this.penWidth; + si.showCoords = this.showCoords; + return si; + } + public StyleInfo() + { + pft = PolyFillType.pftNonZero; + brushClr = Color.AntiqueWhite; + dashArray = null; + penClr = Color.Black; + penWidth = 0.8; + showCoords = false; + } + } + + public class PolyInfo + { + public Polygons polygons; + public StyleInfo si; + } + + public StyleInfo style; + private List PolyInfoList; + const string svg_header = "\n" + + "\n\n" + + "\n\n"; + const string svg_path_format = "\"\n style=\"fill:{0};" + + " fill-opacity:{1:f2}; fill-rule:{2}; stroke:{3};" + + " stroke-opacity:{4:f2}; stroke-width:{5:f2};\"/>\n\n"; + + public SVGBuilder() + { + PolyInfoList = new List(); + style = new StyleInfo(); + } + + public void AddPolygons(Polygons poly) + { + if (poly.Count == 0) return; + PolyInfo pi = new PolyInfo(); + pi.polygons = poly; + pi.si = style.Clone(); + PolyInfoList.Add(pi); + } + + public Boolean SaveToFile(string filename, double scale = 1.0, int margin = 10) + { + if (scale == 0) scale = 1.0; + if (margin < 0) margin = 0; + + //calculate the bounding rect ... + int i = 0, j = 0; + while (i < PolyInfoList.Count) + { + j = 0; + while (j < PolyInfoList[i].polygons.Count && + PolyInfoList[i].polygons[j].Count == 0) j++; + if (j < PolyInfoList[i].polygons.Count) break; + i++; + } + if (i == PolyInfoList.Count) return false; + IntRect rec = new IntRect(); + rec.left = PolyInfoList[i].polygons[j][0].X; + rec.right = rec.left; + rec.top = PolyInfoList[0].polygons[j][0].Y; + rec.bottom = rec.top; + + for (; i < PolyInfoList.Count; i++) + { + foreach (Polygon pg in PolyInfoList[i].polygons) + foreach (IntPoint pt in pg) + { + if (pt.X < rec.left) rec.left = pt.X; + else if (pt.X > rec.right) rec.right = pt.X; + if (pt.Y < rec.top) rec.top = pt.Y; + else if (pt.Y > rec.bottom) rec.bottom = pt.Y; + } + } + + rec.left = (Int64)((double)rec.left * scale); + rec.top = (Int64)((double)rec.top * scale); + rec.right = (Int64)((double)rec.right * scale); + rec.bottom = (Int64)((double)rec.bottom * scale); + Int64 offsetX = -rec.left + margin; + Int64 offsetY = -rec.top + margin; + + StreamWriter writer = new StreamWriter(filename); + if (writer == null) return false; + writer.Write(svg_header, + (rec.right - rec.left) + margin * 2, + (rec.bottom - rec.top) + margin * 2, + (rec.right - rec.left) + margin * 2, + (rec.bottom - rec.top) + margin * 2); + + foreach (PolyInfo pi in PolyInfoList) + { + writer.Write(" \n\n"); + foreach (Polygon p in pi.polygons) + { + foreach (IntPoint pt in p) + { + Int64 x = pt.X; + Int64 y = pt.Y; + writer.Write(String.Format( + "{2},{3}\n", + (int)(x * scale + offsetX), (int)(y * scale + offsetY), x, y)); + + } + writer.Write("\n"); + } + writer.Write("\n"); + } + } + writer.Write("\n"); + writer.Close(); + return true; + } + } + + //------------------------------------------------------------------------------ + //------------------------------------------------------------------------------ + + static private System.Drawing.PointF[] PolygonToPointFArray(Polygon pg, int scale) + { + System.Drawing.PointF[] result = new System.Drawing.PointF[pg.Count]; + for (int i = 0; i < pg.Count; ++i) + { + result[i].X = (float)pg[i].X / scale; + result[i].Y = (float)pg[i].Y / scale; + } + return result; + } + //--------------------------------------------------------------------- + + public Form1() + { + InitializeComponent(); + this.MouseWheel += new MouseEventHandler(Form1_MouseWheel); + } + //--------------------------------------------------------------------- + + private void Form1_MouseWheel(object sender, MouseEventArgs e) + { + if (e.Delta > 0 && nudOffset.Value < 10) nudOffset.Value += (decimal)0.5; + else if (e.Delta < 0 && nudOffset.Value > -10) nudOffset.Value -= (decimal)0.5; + } + //--------------------------------------------------------------------- + + private void bRefresh_Click(object sender, EventArgs e) + { + DrawBitmap(); + } + //--------------------------------------------------------------------- + + private void GenerateAustPlusRandomEllipses(int count) + { + subjects.Clear(); + //load map of Australia from resource ... + _assembly = Assembly.GetExecutingAssembly(); + polyStream = _assembly.GetManifestResourceStream("GuiDemo.aust.bin"); + int len = (int)polyStream.Length; + byte[] b = new byte[len]; + polyStream.Read(b, 0, len); + int polyCnt = BitConverter.ToInt32(b, 0); + int k = 4; + for (int i = 0; i < polyCnt; ++i) + { + int vertCnt = BitConverter.ToInt32(b, k); + k += 4; + Polygon pg = new Polygon(vertCnt); + for (int j = 0; j < vertCnt; ++j) + { + float x = BitConverter.ToSingle(b, k) * scale; + float y = BitConverter.ToSingle(b, k + 4) * scale; + k += 8; + pg.Add(new IntPoint((int)x, (int)y)); + } + subjects.Add(pg); + } + + clips.Clear(); + Random rand = new Random(); + GraphicsPath path = new GraphicsPath(); + Point pt = new Point(); + + const int ellipse_size = 100, margin = 10; + for (int i = 0; i < count; ++i) + { + int w = pictureBox1.ClientRectangle.Width - ellipse_size - margin *2; + int h = pictureBox1.ClientRectangle.Height - ellipse_size - margin * 2 - statusStrip1.Height; + + pt.X = rand.Next(w) + margin; + pt.Y = rand.Next(h) + margin; + int size = rand.Next(ellipse_size - 20) + 20; + path.Reset(); + path.AddEllipse(pt.X, pt.Y, size, size); + path.Flatten(); + Polygon clip = new Polygon(path.PathPoints.Count()); + foreach (PointF p in path.PathPoints) + clip.Add(new IntPoint((int)(p.X * scale), (int)(p.Y * scale))); + clips.Add(clip); + } + } + //--------------------------------------------------------------------- + + private IntPoint GenerateRandomPoint(int l, int t, int r, int b, Random rand) + { + int Q = 10; + IntPoint newPt = new IntPoint(); + newPt.X = (rand.Next(r / Q) * Q + l + 10) * scale; + newPt.Y = (rand.Next(b / Q) * Q + t + 10) * scale; + return newPt; + } + //--------------------------------------------------------------------- + + private void GenerateRandomPolygon(int count) + { + int Q = 10; + Random rand = new Random(); + int l = 10; + int t = 10; + int r = (pictureBox1.ClientRectangle.Width - 20) / Q * Q; + int b = (pictureBox1.ClientRectangle.Height - 20) / Q * Q; + + subjects.Clear(); + clips.Clear(); + + Polygon subj = new Polygon(count); + for (int i = 0; i < count; ++i) + subj.Add(GenerateRandomPoint(l, t, r, b, rand)); + subjects.Add(subj); + + Polygon clip = new Polygon(count); + for (int i = 0; i < count; ++i) + clip.Add(GenerateRandomPoint(l, t, r, b, rand)); + clips.Add(clip); + } + //--------------------------------------------------------------------- + + ClipType GetClipType() + { + if (rbIntersect.Checked) return ClipType.ctIntersection; + if (rbUnion.Checked) return ClipType.ctUnion; + if (rbDifference.Checked) return ClipType.ctDifference; + else return ClipType.ctXor; + } + //--------------------------------------------------------------------- + + PolyFillType GetPolyFillType() + { + if (rbNonZero.Checked) return PolyFillType.pftNonZero; + else return PolyFillType.pftEvenOdd; + } + //--------------------------------------------------------------------- + + bool LoadFromFile(string filename, Polygons ppg, double scale = 0, + int xOffset = 0, int yOffset = 0) + { + double scaling = Math.Pow(10, scale); + ppg.Clear(); + if (!File.Exists(filename)) return false; + StreamReader sr = new StreamReader(filename); + if (sr == null) return false; + string line; + if ((line = sr.ReadLine()) == null) return false; + int polyCnt, vertCnt; + if (!Int32.TryParse(line, out polyCnt) || polyCnt < 0) return false; + ppg.Capacity = polyCnt; + for (int i = 0; i < polyCnt; i++) + { + if ((line = sr.ReadLine()) == null) return false; + if (!Int32.TryParse(line, out vertCnt) || vertCnt < 0) return false; + Polygon pg = new Polygon(vertCnt); + ppg.Add(pg); + for (int j = 0; j < vertCnt; j++) + { + double x, y; + if ((line = sr.ReadLine()) == null) return false; + char[] delimiters = new char[] { ',', ' ' }; + string [] vals = line.Split(delimiters); + if (vals.Length < 2) return false; + if (!double.TryParse(vals[0], out x)) return false; + if (!double.TryParse(vals[1], out y)) + if (vals.Length < 2 || !double.TryParse(vals[2], out y)) return false; + x = x * scaling + xOffset; + y = y * scaling + yOffset; + pg.Add(new IntPoint((int)Math.Round(x), (int)Math.Round(y))); + } + } + return true; + } + //------------------------------------------------------------------------------ + + void SaveToFile(string filename, Polygons ppg, int scale = 0) + { + double scaling = Math.Pow(10, scale); + StreamWriter writer = new StreamWriter(filename); + if (writer == null) return; + writer.Write("{0}\n", ppg.Count); + foreach (Polygon pg in ppg) + { + writer.Write("{0}\n", pg.Count); + foreach (IntPoint ip in pg) + writer.Write("{0:0.0000}, {1:0.0000}\n", + (double)ip.X/scaling, (double)ip.Y/scaling); + } + writer.Close(); + } + //--------------------------------------------------------------------------- + + private void DrawBitmap(bool justClip = false) + { + + if (!justClip) + { + if (rbTest2.Checked) + GenerateAustPlusRandomEllipses((int)nudCount.Value); + else + GenerateRandomPolygon((int)nudCount.Value); + } + + Cursor.Current = Cursors.WaitCursor; + Graphics newgraphic; + newgraphic = Graphics.FromImage(mybitmap); + newgraphic.SmoothingMode = SmoothingMode.AntiAlias; + newgraphic.Clear(Color.White); + + GraphicsPath path = new GraphicsPath(); + if (rbNonZero.Checked) path.FillMode = FillMode.Winding; + + //draw subjects ... + foreach (Polygon pg in subjects) + { + PointF[] pts = PolygonToPointFArray(pg, scale); + path.AddPolygon(pts); + pts = null; + } + Pen myPen = new Pen(Color.FromArgb(196, 0xC3, 0xC9, 0xCF), (float)0.6); + SolidBrush myBrush = new SolidBrush(Color.FromArgb(127, 0xDD, 0xDD, 0xF0)); + newgraphic.FillPath(myBrush, path); + newgraphic.DrawPath(myPen, path); + path.Reset(); + + //draw clips ... + if (rbNonZero.Checked) path.FillMode = FillMode.Winding; + foreach (Polygon pg in clips) + { + PointF[] pts = PolygonToPointFArray(pg, scale); + path.AddPolygon(pts); + pts = null; + } + myPen.Color = Color.FromArgb(196, 0xF9, 0xBE, 0xA6); + myBrush.Color = Color.FromArgb(127, 0xFF, 0xE0, 0xE0); + newgraphic.FillPath(myBrush, path); + newgraphic.DrawPath(myPen, path); + + //do the clipping ... + if ((clips.Count > 0 || subjects.Count > 0) && !rbNone.Checked) + { + Polygons solution2 = new Polygons(); + Clipper c = new Clipper(); + c.AddPolygons(subjects, PolyType.ptSubject); + c.AddPolygons(clips, PolyType.ptClip); + solution.Clear(); + bool succeeded = c.Execute(GetClipType(), solution, GetPolyFillType(), GetPolyFillType()); + if (succeeded) + { + myBrush.Color = Color.Black; + path.Reset(); + + //It really shouldn't matter what FillMode is used for solution + //polygons because none of the solution polygons overlap. + //However, FillMode.Winding will show any orientation errors where + //holes will be stroked (outlined) correctly but filled incorrectly ... + path.FillMode = FillMode.Winding; + + //or for something fancy ... + if (nudOffset.Value != 0) + solution2 = Clipper.OffsetPolygons(solution, (double)nudOffset.Value * scale, JoinType.jtMiter); + else + solution2 = new Polygons(solution); + foreach (Polygon pg in solution2) + { + PointF[] pts = PolygonToPointFArray(pg, scale); + if (pts.Count() > 2) + path.AddPolygon(pts); + pts = null; + } + myBrush.Color = Color.FromArgb(127, 0x66, 0xEF, 0x7F); + myPen.Color = Color.FromArgb(255, 0, 0x33, 0); + myPen.Width = 1.0f; + newgraphic.FillPath(myBrush, path); + newgraphic.DrawPath(myPen, path); + + //now do some fancy testing ... + Font f = new Font("Arial", 8); + SolidBrush b = new SolidBrush(Color.Navy); + double subj_area = 0, clip_area = 0, int_area = 0, union_area = 0; + c.Clear(); + c.AddPolygons(subjects, PolyType.ptSubject); + c.Execute(ClipType.ctUnion, solution2, GetPolyFillType(), GetPolyFillType()); + foreach (Polygon pg in solution2) subj_area += Clipper.Area(pg); + c.Clear(); + c.AddPolygons(clips, PolyType.ptClip); + c.Execute(ClipType.ctUnion, solution2, GetPolyFillType(), GetPolyFillType()); + foreach (Polygon pg in solution2) clip_area += Clipper.Area(pg); + c.AddPolygons(subjects, PolyType.ptSubject); + c.Execute(ClipType.ctIntersection, solution2, GetPolyFillType(), GetPolyFillType()); + foreach (Polygon pg in solution2) int_area += Clipper.Area(pg); + c.Execute(ClipType.ctUnion, solution2, GetPolyFillType(), GetPolyFillType()); + foreach (Polygon pg in solution2) union_area += Clipper.Area(pg); + + StringFormat lftStringFormat = new StringFormat(); + lftStringFormat.Alignment = StringAlignment.Near; + lftStringFormat.LineAlignment = StringAlignment.Near; + StringFormat rtStringFormat = new StringFormat(); + rtStringFormat.Alignment = StringAlignment.Far; + rtStringFormat.LineAlignment = StringAlignment.Near; + Rectangle rec = new Rectangle(pictureBox1.ClientSize.Width - 108, + pictureBox1.ClientSize.Height - 116, 104, 106); + newgraphic.FillRectangle(new SolidBrush(Color.FromArgb(196, Color.WhiteSmoke)), rec); + newgraphic.DrawRectangle(myPen, rec); + rec.Inflate(new Size(-2, 0)); + newgraphic.DrawString("Areas", f, b, rec, rtStringFormat); + rec.Offset(new Point(0, 14)); + newgraphic.DrawString("subj: ", f, b, rec, lftStringFormat); + newgraphic.DrawString((subj_area / 100000).ToString("0,0"), f, b, rec, rtStringFormat); + rec.Offset(new Point(0, 12)); + newgraphic.DrawString("clip: ", f, b, rec, lftStringFormat); + newgraphic.DrawString((clip_area / 100000).ToString("0,0"), f, b, rec, rtStringFormat); + rec.Offset(new Point(0, 12)); + newgraphic.DrawString("intersect: ", f, b, rec, lftStringFormat); + newgraphic.DrawString((int_area / 100000).ToString("0,0"), f, b, rec, rtStringFormat); + rec.Offset(new Point(0, 12)); + newgraphic.DrawString("---------", f, b, rec, rtStringFormat); + rec.Offset(new Point(0, 10)); + newgraphic.DrawString("s + c - i: ", f, b, rec, lftStringFormat); + newgraphic.DrawString(((subj_area + clip_area - int_area) / 100000).ToString("0,0"), f, b, rec, rtStringFormat); + rec.Offset(new Point(0, 10)); + newgraphic.DrawString("---------", f, b, rec, rtStringFormat); + rec.Offset(new Point(0, 10)); + newgraphic.DrawString("union: ", f, b, rec, lftStringFormat); + newgraphic.DrawString((union_area / 100000).ToString("0,0"), f, b, rec, rtStringFormat); + rec.Offset(new Point(0, 10)); + newgraphic.DrawString("---------", f, b, rec, rtStringFormat); + } //end if succeeded + } //end if something to clip + + pictureBox1.Image = mybitmap; + newgraphic.Dispose(); + Cursor.Current = Cursors.Default; + } + //--------------------------------------------------------------------- + + private void Form1_Load(object sender, EventArgs e) + { + mybitmap = new Bitmap( + pictureBox1.ClientRectangle.Width, + pictureBox1.ClientRectangle.Height, + PixelFormat.Format32bppArgb); + + subjects = new Polygons(); + clips = new Polygons(); + solution = new Polygons(); + + toolStripStatusLabel1.Text = + "Tip: Use the mouse-wheel (or +,-,0) to adjust the offset of the solution polygons."; + DrawBitmap(); + } + //--------------------------------------------------------------------- + + private void bClose_Click(object sender, EventArgs e) + { + Close(); + } + //--------------------------------------------------------------------- + + private void Form1_Resize(object sender, EventArgs e) + { + if (pictureBox1.ClientRectangle.Width == 0 || + pictureBox1.ClientRectangle.Height == 0) return; + mybitmap.Dispose(); + mybitmap = new Bitmap( + pictureBox1.ClientRectangle.Width, + pictureBox1.ClientRectangle.Height, + PixelFormat.Format32bppArgb); + pictureBox1.Image = mybitmap; + DrawBitmap(); + } + //--------------------------------------------------------------------- + + private void rbNonZero_Click(object sender, EventArgs e) + { + DrawBitmap(true); + } + //--------------------------------------------------------------------- + + private void Form1_KeyDown(object sender, KeyEventArgs e) + { + switch (e.KeyCode) + { + case (Keys)27: + this.Close(); + return; + case Keys.F1: + MessageBox.Show(this.Text + "\nby Angus Johnson\nCopyright © 2010, 2011", + this.Text, MessageBoxButtons.OK, MessageBoxIcon.Information); + e.Handled = true; + return; + case (Keys)187: + case Keys.Add: + if (nudOffset.Value == 10) return; + nudOffset.Value += (decimal)0.5; + e.Handled = true; + break; + case (Keys)189: + case Keys.Subtract: + if (nudOffset.Value == -10) return; + nudOffset.Value -= (decimal)0.5; + e.Handled = true; + break; + case Keys.NumPad0: + case Keys.D0: + if (nudOffset.Value == 0) return; + nudOffset.Value = (decimal)0; + e.Handled = true; + break; + default: return; + } + + } + //--------------------------------------------------------------------- + + private void nudCount_ValueChanged(object sender, EventArgs e) + { + DrawBitmap(true); + } + //--------------------------------------------------------------------- + + private void rbTest1_Click(object sender, EventArgs e) + { + if (rbTest1.Checked) + lblCount.Text = "Vertex &Count:"; + else + lblCount.Text = "Ellipse &Count:"; + DrawBitmap(); + } + //--------------------------------------------------------------------- + + private void bSave_Click(object sender, EventArgs e) + { + //save to SVG ... + if (saveFileDialog1.ShowDialog() == DialogResult.OK) + { + PolyFillType pft = GetPolyFillType(); + SVGBuilder svg = new SVGBuilder(); + svg.style.brushClr = Color.FromArgb(0x10, 0, 0, 0x9c); + svg.style.penClr = Color.FromArgb(0xd3, 0xd3, 0xda); + svg.AddPolygons(subjects); + svg.style.brushClr = Color.FromArgb(0x10, 0x9c, 0, 0); + svg.style.penClr = Color.FromArgb(0xff, 0xa0, 0x7a); + svg.AddPolygons(clips); + svg.style.brushClr = Color.FromArgb(0xAA, 0x80, 0xff, 0x9c); + svg.style.penClr = Color.FromArgb(0, 0x33, 0); + svg.AddPolygons(solution); + svg.SaveToFile(saveFileDialog1.FileName, 1.0 / scale); + } + } + //--------------------------------------------------------------------- + + } +} diff --git a/clipper/C#/GuiDemo/GuiDemo/Form1.resx b/clipper/C#/GuiDemo/GuiDemo/Form1.resx new file mode 100755 index 0000000..2f4b5d7 --- /dev/null +++ b/clipper/C#/GuiDemo/GuiDemo/Form1.resx @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 127, 17 + + + 25 + + + + + AAABAAIAICAAAAAAAACoEAAAJgAAABAQAAAAAAAAaAQAAM4QAAAoAAAAIAAAAEAAAAABACAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7A72bOwO9EwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwO9YzsDvf8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsDvTM8Bb35PAS9/AAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMFRAxPBUQM3wVEDT8FRA0+dPDVGLAWG6zsD + vf8+B77jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwO9AwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwVEDD8FRA3fBUgXJwVED/8FRA//BUQP/v1AF/y4M + RP8AAAD/Eg0m/y4HevTBUQMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAA7A71TOwO9nzsDvT8AAAAAAAAAAAAAAAAAAAAAAAAAAMFRA2vBUgXtwVED/8VhGPzQik7/16Rw/9uz + hP9YOWf/AAAA/yZIS/8LFRb/AAAA/6dHBOXBUQNXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAA7A72zOwO9/z0GvuI7A71/NgOsIhMIAFPBUgS0wVED/8hqJP3bs4T/5ty6/+bc + uv/m3Lr/i3iT/wICBv8kREf/eOPs/1uttP8AAAD/rlgZ/cFRA//BUQObwVEDAwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAADsDvRM8Bb3tOwO9/zsDvf8eAl//AAAA/xsNEf+NZUb/5ty6/+bc + uv/m3Lr/5ty6/7ytqf8HBBL/FSUp/3Tc5f955u//W620/wAAAP/Kv6L/0YxR/8FRA//CUwbCwVEDAwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsDvVM7A73/VDan/wICA/8cNTj/CxUW/wAA + AP8XEiT/gnOB/+bcuv/VyrL/Egon/wkQEv9qytL/eebv/3nm7/9brbT/AAAA/8rBo//m3Lr/2KZz/8FR + A//BUQOTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoDuqwOAir/Dhga/3LY + 4f924Oj/RIKH/wsVFv8AAAD/EAkl/xsPNv8CAwT/W620/3nm7/955u//eebv/1uttP8AAAD/ysGj/+bc + uv/m3Lr/0YxR/8FRA//BUQNLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGggZZgAA + AP87cHT/eebv/3nm7/955u//duDo/0SCh/8LFRb/AAAA/0qNkv955u//eebv/3nm7/955u//W620/wAA + AP/KwaP/5ty6/+bcuv/m2rj/xmMb/MFSBNjBUQMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAC8TwN5KxER/wUHCv9jvMP/eebv/3nm7/955u//eebv/3bg6P9Voqn/eebv/3nm7/955u//eebv/3nm + 7/9brbT/AAAA/8rBo//m3Lr/5ty6/+bcuv/Zqnn/wVED/8FRA1cAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAMFSBcm9aSj6EQ0b/w4XHf9y2OH/eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm + 7/955u//eebv/1uttP8AAAD/oI2h/+bcuv/m3Lr/5ty6/+bcuv/EXBL7wVEDpwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAADBUQMTwVED/9SXX/+6r57/BAIJ/yE8Qv944+z/eebv/3nm7/955u//eebv/3nm + 7/955u//eebv/3nm7/955u//W620/wAAAP8EBAr/GAs5/1M/cP+xoaT/5ty6/8+FSP/BUgT1AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAMFRAzvBUQP/27OE/+bcuv+FdYT/AAAA/ztvdP955u//eebv/3nm + 7/955u//eebv/3nm7/955u//eebv/3nm7/924Oj/Upuh/y9aXv8NGRr/AAAA/wAAAP8OCxz/Pic9/4M1 + Dv+uSQMeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwVEDU8FRA//fwpj/5ty6/+TZuv8cDTv/AAAA/2bD + yv955u//eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm7/9lv8f/Qn6D/yA9 + QP8EBgf/AAAA/zsIlcw7A71bOwO9BwAAAAAAAAAAAAAAAAAAAADBUQNTwVED/9/CmP/m3Lr/Xkh8/wAA + AP8xXmH/eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm + 7/955u//eebv/1emrP8AAAD/NAOm/zsDvf87A73zOwO9jwAAAAAAAAAAAAAAAMFRAzvBUQP/27OE/4Vw + k/8DAwn/ID1A/3jj7P955u//eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm + 7/903OX/V6as/zlscP8aMTT/AgME/wAAAP83CJ3POwO9dzsDvTcAAAAAAAAAAAAAAAAAAAAAwVEDE8FR + A/+ibGb/BwQS/xMiJf903OX/eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm7/955u//duDo/1em + rP85bHD/GjE0/wIDBP8AAAD/AgED/x4XKv9OLjH/hTgJ+AAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAqUUZ0A0EH/8LExb/aMbO/3nm7/955u//eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm + 7/9brbT/AAAA/wEBA/8bFCr/UENY/5uQhv/c07L/5ty6/8RcEvvBUQOnAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAACkChAoVBS72AgME/12xuP955u//eebv/3nm7/955u//duDo/1+0u/955u//eebv/3nm + 7/955u//eebv/1uttP8AAAD/w7qe/+bcuv/m3Lr/5ty6/+bcuv/Zqnn/wVED/8FRA1cAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAIAJnwAAAAP8/d3z/eebv/3nm7/9mw8r/RoWL/yJAQ/8DBgf/AAAA/1Wi + qf955u//eebv/3nm7/955u//W620/wAAAP/KwaP/5ty6/+bcuv/m3Lr/5tq4/8ZjG/zBUgTYwVEDAwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAADsDvXM4A7L/BgMM/xowNP8xXmH/Dhod/wAAAP8AAAD/FRIb/1pS + Uv8uKyz/DBQZ/3LY4f955u//eebv/3nm7/9brbT/AAAA/8rBo//m3Lr/5ty6/+bcuv/RjFH/wVED/8FR + A0sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7A71HPAS9/DsDvf8eAl//AAAA/wIBAv8mDw//dV9L/8a+ + oP/m3Lr/5ty6/8rBo/8GBgn/LVRZ/3nm7/955u//eebv/1uttP8AAAD/ysGj/+bcuv/m3Lr/2KZz/8FR + A//BUQOTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwO9JzsDvec7A723OwO9aykCgjAAAABXYCgBFsJU + BtvBUwX+1Ztl/+bcuv/m3Lr/5ty6/4N8bP8AAAD/VaKp/3nm7/955u//W620/wAAAP/KwaP/5tq4/9GO + U//BUQP/wlMGwsFRAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7A70jOwO9BwAAAAAAAAAAAAAAAAAA + AAAAAAAAwVEDC8FSBLTBUQP/ynMw/t26jf/m3Lr/49m4/yomKf8MFBn/ctjh/3nm7/9brbT/AAAA/8Cb + cf/IaiT9wVED/8FRA5vBUQMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAMFRA2vBUgXxwVED/8ZjG/zQik7/totf/wQDBv8pTVL/W620/0SC + h/8AAAD/qkcD/8FTBebBUQNXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMFRAw/BUQN3wVIFycFRA//BUQP/ZysG/wAA + AP8AAAD/AAAA/wAAAP+RPQJ9wVEDBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwVEDE8FR + Azu6TgNVSBdAnywCjv8sAo7/JAJynwAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAOwO9nzsDvf87A71/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7A70PPAS95DsDvXsAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7A71TOwO9XwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//8////+f////H///4B/+/wAP/j4AB/8AA + AH/AAAA/4AAAP/AAAB/wAAAP8AAAD/AAAA/gAAAP4AAAB+AAAAHgAAAA4AAAAeAAAAfwAAAP4AAAD+AA + AA/AAAAfgAAAPwAAAD8+AAB//4AB///AA///+Af///+P////j////8//KAAAABAAAAAgAAAAAQAgAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsD + vRk4A6/kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBUQMFwVEDIrBH + GiU3BK3GOASv5gAAAAAAAAAAAAAAAAAAAAAAAAAAOwO9FjgDrts4BK5VAAAAAMFRAxvBUQSdxmQd8c1+ + P/9RJSz/ERsi/0UZKrjBUQMWAAAAAAAAAAAAAAAAAAAAAAAAAAA4BK/wOASw9xICMJ2GRBns46OF/+Oj + hf+NgYL/LFJX/2rJ0f9eRi//xWEZ5sJTBjIAAAAAAAAAAAAAAAAAAAAAOwO9FTURjuooSk7/MV5h/ykn + L/96cHL/HjM8/3Xf6P9qytL/ZWFS/+Ojhf/FYRnkwVEDEwAAAAAAAAAAAAAAAAAAAAAyFAq3R4aM/3nm + 7/9rzNP/NmZq/23Q2P955u//asrS/2VhUv/jo4X/46OF/8FRA4wAAAAAAAAAAAAAAADBUQMFxWol8Dc1 + OP9ht8D/eebv/3nm7/955u//eebv/2rK0v8pJCv/joGH/+Ojhf/GYxvmAAAAAAAAAAAAAAAAwVEDJM+G + Sf/jo4X/Fh8s/3Td5v955u//eebv/3nm7/945e3/XbC3/ztwdP8cMjj/MRkV/zgErN87A70CAAAAAMFR + AyTPhkn/c2Z1/zJgY/955u//eebv/3nm7/955u//eebv/3nm7/9v1Nz/UZqh/xYqLP84BK7ROAOv5AAA + AADBUQMFhUEp8yNAR/945O3/eebv/3nm7/955u//eebv/0qNkv8cLTT/OzY5/3lyZv+SRBXmAAAAAQAA + AAAAAAAAIAJoMxYgK/1y2eH/aMXN/0WDiP9Lj5X/eebv/3nm7/8uV1r/46OF/+Ojhf/jo4X/wVEDjAAA + AAAAAAAAOwO9EjoDutsQDSj/GiIk/09HO/+Ph3j/Q0I8/2S+xv955u//Llda/+Ojhf/jo4X/xWEZ5MFR + AxMAAAAAAAAAADgDrt84A67eDwEuIrlQBT/GZh7s46OF/+Ojhf8jNzv/d+Ps/y5XWv/jo4X/xWIZ5sJT + BjIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwVEDG8FRBJ7GZR7xeUMc/yE/Qv8RISL/rkoEmsFR + AxYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwVEDBb1PAyQ4BKnqNwOr9AAA + AAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwO9BDgD + r+gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/nwAA/B8AABAPAACABwAAgAMAAMADAACAAwAAgAAAAIAA + AACAAQAAgAMAAAADAAAABwAA8A8AAPwfAAD/PwAA + + + \ No newline at end of file diff --git a/clipper/C#/GuiDemo/GuiDemo/GuiDemo.csproj b/clipper/C#/GuiDemo/GuiDemo/GuiDemo.csproj new file mode 100755 index 0000000..d592685 --- /dev/null +++ b/clipper/C#/GuiDemo/GuiDemo/GuiDemo.csproj @@ -0,0 +1,135 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {8BD44147-3290-4A73-BAA2-1C171566BC25} + WinExe + Properties + GuiDemo + GuiDemo + v4.0 + + + 512 + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + + + + + + + + + + + + Form + + + Form1.cs + + + + + + Form1.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + False + Microsoft .NET Framework 4 Client Profile %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + {9B062971-A88E-4A3D-B3C9-12B78D15FA66} + clipper_library + + + + + \ No newline at end of file diff --git a/clipper/C#/GuiDemo/GuiDemo/Program.cs b/clipper/C#/GuiDemo/GuiDemo/Program.cs new file mode 100755 index 0000000..cc4a2c7 --- /dev/null +++ b/clipper/C#/GuiDemo/GuiDemo/Program.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; + +namespace WindowsFormsApplication1 +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/clipper/C#/GuiDemo/GuiDemo/Properties/AssemblyInfo.cs b/clipper/C#/GuiDemo/GuiDemo/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..8d8be3c --- /dev/null +++ b/clipper/C#/GuiDemo/GuiDemo/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ClipperCSharpDemo1")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Angus Johnson")] +[assembly: AssemblyProduct("ClipperCSharpDemo1")] +[assembly: AssemblyCopyright("Copyright © Angus Johnson 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7eac3b0b-6f6f-4b48-b26b-c7acb1f769f5")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/clipper/C#/GuiDemo/GuiDemo/Properties/Resources.Designer.cs b/clipper/C#/GuiDemo/GuiDemo/Properties/Resources.Designer.cs new file mode 100755 index 0000000..84210c0 --- /dev/null +++ b/clipper/C#/GuiDemo/GuiDemo/Properties/Resources.Designer.cs @@ -0,0 +1,70 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.235 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace GuiDemo.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GuiDemo.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static byte[] aust { + get { + object obj = ResourceManager.GetObject("aust", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/clipper/C#/GuiDemo/GuiDemo/Properties/Resources.resx b/clipper/C#/GuiDemo/GuiDemo/Properties/Resources.resx new file mode 100755 index 0000000..4ab87d2 --- /dev/null +++ b/clipper/C#/GuiDemo/GuiDemo/Properties/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\aust.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/clipper/C#/GuiDemo/GuiDemo/Properties/Settings.Designer.cs b/clipper/C#/GuiDemo/GuiDemo/Properties/Settings.Designer.cs new file mode 100755 index 0000000..e31c5b7 --- /dev/null +++ b/clipper/C#/GuiDemo/GuiDemo/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.235 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace GuiDemo.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/clipper/C#/GuiDemo/GuiDemo/Properties/Settings.settings b/clipper/C#/GuiDemo/GuiDemo/Properties/Settings.settings new file mode 100755 index 0000000..abf36c5 --- /dev/null +++ b/clipper/C#/GuiDemo/GuiDemo/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/clipper/C#/GuiDemo/GuiDemo/Settings.cs b/clipper/C#/GuiDemo/GuiDemo/Settings.cs new file mode 100755 index 0000000..dfa1e2a --- /dev/null +++ b/clipper/C#/GuiDemo/GuiDemo/Settings.cs @@ -0,0 +1,28 @@ +namespace WindowsFormsApplication1.Properties { + + + // This class allows you to handle specific events on the settings class: + // The SettingChanging event is raised before a setting's value is changed. + // The PropertyChanged event is raised after a setting's value is changed. + // The SettingsLoaded event is raised after the setting values are loaded. + // The SettingsSaving event is raised before the setting values are saved. + internal sealed partial class Settings { + + public Settings() { + // // To add event handlers for saving and changing settings, uncomment the lines below: + // + // this.SettingChanging += this.SettingChangingEventHandler; + // + // this.SettingsSaving += this.SettingsSavingEventHandler; + // + } + + private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) { + // Add code to handle the SettingChangingEvent event here. + } + + private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) { + // Add code to handle the SettingsSaving event here. + } + } +} diff --git a/clipper/C#/GuiDemo/GuiDemo/aust.bin b/clipper/C#/GuiDemo/GuiDemo/aust.bin new file mode 100755 index 0000000..9d3bd08 Binary files /dev/null and b/clipper/C#/GuiDemo/GuiDemo/aust.bin differ diff --git a/clipper/C#/GuiDemo/GuiDemo/bin/Release/GuiDemo.exe b/clipper/C#/GuiDemo/GuiDemo/bin/Release/GuiDemo.exe new file mode 100755 index 0000000..ed9640d Binary files /dev/null and b/clipper/C#/GuiDemo/GuiDemo/bin/Release/GuiDemo.exe differ diff --git a/clipper/C#/GuiDemo/GuiDemo/bin/Release/clipper_library.dll b/clipper/C#/GuiDemo/GuiDemo/bin/Release/clipper_library.dll new file mode 100755 index 0000000..18887a4 Binary files /dev/null and b/clipper/C#/GuiDemo/GuiDemo/bin/Release/clipper_library.dll differ diff --git a/clipper/C#/clipper_library/Properties/AssemblyInfo.cs b/clipper/C#/clipper_library/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..cb9c080 --- /dev/null +++ b/clipper/C#/clipper_library/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("clipper_library")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("clipper_library")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("51a6bdca-bc4e-4b2c-ae69-36e2497204f2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/clipper/C#/clipper_library/clipper.cs b/clipper/C#/clipper_library/clipper.cs new file mode 100755 index 0000000..3d6b901 --- /dev/null +++ b/clipper/C#/clipper_library/clipper.cs @@ -0,0 +1,3695 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 5.1.0 * +* Date : 1 February 2013 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2013 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +/******************************************************************************* +* * +* This is a translation of the Delphi Clipper library and the naming style * +* used has retained a Delphi flavour. * +* * +*******************************************************************************/ + +using System; +using System.Collections.Generic; +//using System.Text; //for Int128.AsString() & StringBuilder + +namespace ClipperLib +{ + + + using Polygon = List; + using Polygons = List>; + + + //------------------------------------------------------------------------------ + // PolyTree & PolyNode classes + //------------------------------------------------------------------------------ + + public class PolyTree : PolyNode + { + internal List m_AllPolys = new List(); + + ~PolyTree() + { + Clear(); + } + + public void Clear() + { + for (int i = 0; i < m_AllPolys.Count; i++) + m_AllPolys[i] = null; + m_AllPolys.Clear(); + m_Childs.Clear(); + } + + public PolyNode GetFirst() + { + if (m_Childs.Count > 0) + return m_Childs[0]; + else + return null; + } + + public int Total + { + get { return m_AllPolys.Count; } + } + + } + + public class PolyNode + { + internal PolyNode m_Parent; + internal Polygon m_polygon = new Polygon(); + internal int m_Index; + internal List m_Childs = new List(); + + private bool IsHoleNode() + { + bool result = true; + PolyNode node = m_Parent; + while (node != null) + { + result = !result; + node = node.m_Parent; + } + return result; + } + + public int ChildCount + { + get { return m_Childs.Count; } + } + + public Polygon Contour + { + get { return m_polygon; } + } + + internal void AddChild(PolyNode Child) + { + int cnt = m_Childs.Count; + m_Childs.Add(Child); + Child.m_Parent = this; + Child.m_Index = cnt; + } + + public PolyNode GetNext() + { + if (m_Childs.Count > 0) + return m_Childs[0]; + else + return GetNextSiblingUp(); + } + + internal PolyNode GetNextSiblingUp() + { + if (m_Parent == null) + return null; + else if (m_Index == m_Parent.m_Childs.Count - 1) + return m_Parent.GetNextSiblingUp(); + else + return m_Parent.m_Childs[m_Index + 1]; + } + + public List Childs + { + get { return m_Childs; } + } + + public PolyNode Parent + { + get { return m_Parent; } + } + + public bool IsHole + { + get { return IsHoleNode(); } + } + } + + + //------------------------------------------------------------------------------ + // Int128 struct (enables safe math on signed 64bit integers) + // eg Int128 val1((Int64)9223372036854775807); //ie 2^63 -1 + // Int128 val2((Int64)9223372036854775807); + // Int128 val3 = val1 * val2; + // val3.ToString => "85070591730234615847396907784232501249" (8.5e+37) + //------------------------------------------------------------------------------ + + internal struct Int128 + { + private Int64 hi; + private UInt64 lo; + + public Int128(Int64 _lo) + { + lo = (UInt64)_lo; + if (_lo < 0) hi = -1; + else hi = 0; + } + + public Int128(Int64 _hi, UInt64 _lo) + { + lo = _lo; + hi = _hi; + } + + public Int128(Int128 val) + { + hi = val.hi; + lo = val.lo; + } + + public bool IsNegative() + { + return hi < 0; + } + + public static bool operator ==(Int128 val1, Int128 val2) + { + if ((object)val1 == (object)val2) return true; + else if ((object)val1 == null || (object)val2 == null) return false; + return (val1.hi == val2.hi && val1.lo == val2.lo); + } + + public static bool operator!= (Int128 val1, Int128 val2) + { + return !(val1 == val2); + } + + public override bool Equals(System.Object obj) + { + if (obj == null || !(obj is Int128)) + return false; + Int128 i128 = (Int128)obj; + return (i128.hi == hi && i128.lo == lo); + } + + public override int GetHashCode() + { + return hi.GetHashCode() ^ lo.GetHashCode(); + } + + public static bool operator> (Int128 val1, Int128 val2) + { + if (val1.hi != val2.hi) + return val1.hi > val2.hi; + else + return val1.lo > val2.lo; + } + + public static bool operator< (Int128 val1, Int128 val2) + { + if (val1.hi != val2.hi) + return val1.hi < val2.hi; + else + return val1.lo < val2.lo; + } + + public static Int128 operator+ (Int128 lhs, Int128 rhs) + { + lhs.hi += rhs.hi; + lhs.lo += rhs.lo; + if (lhs.lo < rhs.lo) lhs.hi++; + return lhs; + } + + public static Int128 operator- (Int128 lhs, Int128 rhs) + { + return lhs + -rhs; + } + + public static Int128 operator -(Int128 val) + { + if (val.lo == 0) + return new Int128(-val.hi, 0); + else + return new Int128(~val.hi, ~val.lo +1); + } + + //nb: Constructing two new Int128 objects every time we want to multiply longs + //is slow. So, although calling the Int128Mul method doesn't look as clean, the + //code runs significantly faster than if we'd used the * operator. + + public static Int128 Int128Mul(Int64 lhs, Int64 rhs) + { + bool negate = (lhs < 0) != (rhs < 0); + if (lhs < 0) lhs = -lhs; + if (rhs < 0) rhs = -rhs; + UInt64 int1Hi = (UInt64)lhs >> 32; + UInt64 int1Lo = (UInt64)lhs & 0xFFFFFFFF; + UInt64 int2Hi = (UInt64)rhs >> 32; + UInt64 int2Lo = (UInt64)rhs & 0xFFFFFFFF; + + //nb: see comments in clipper.pas + UInt64 a = int1Hi * int2Hi; + UInt64 b = int1Lo * int2Lo; + UInt64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + UInt64 lo; + Int64 hi; + hi = (Int64)(a + (c >> 32)); + + unchecked { lo = (c << 32) + b; } + if (lo < b) hi++; + Int128 result = new Int128(hi, lo); + return negate ? -result : result; + } + + public static Int128 operator /(Int128 lhs, Int128 rhs) + { + if (rhs.lo == 0 && rhs.hi == 0) + throw new ClipperException("Int128: divide by zero"); + + bool negate = (rhs.hi < 0) != (lhs.hi < 0); + if (lhs.hi < 0) lhs = -lhs; + if (rhs.hi < 0) rhs = -rhs; + + if (rhs < lhs) + { + Int128 result = new Int128(0); + Int128 cntr = new Int128(1); + while (rhs.hi >= 0 && !(rhs > lhs)) + { + rhs.hi <<= 1; + if ((Int64)rhs.lo < 0) rhs.hi++; + rhs.lo <<= 1; + + cntr.hi <<= 1; + if ((Int64)cntr.lo < 0) cntr.hi++; + cntr.lo <<= 1; + } + rhs.lo >>= 1; + if ((rhs.hi & 1) == 1) + rhs.lo |= 0x8000000000000000; + rhs.hi = (Int64)((UInt64)rhs.hi >> 1); + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000; + cntr.hi >>= 1; + + while (cntr.hi != 0 || cntr.lo != 0) + { + if (!(lhs < rhs)) + { + lhs -= rhs; + result.hi |= cntr.hi; + result.lo |= cntr.lo; + } + rhs.lo >>= 1; + if ((rhs.hi & 1) == 1) + rhs.lo |= 0x8000000000000000; + rhs.hi >>= 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000; + cntr.hi >>= 1; + } + return negate ? -result : result; + } + else if (rhs == lhs) + return new Int128(1); + else + return new Int128(0); + } + + public double ToDouble() + { + const double shift64 = 18446744073709551616.0; //2^64 + if (hi < 0) + { + if (lo == 0) + return (double)hi * shift64; + else + return -(double)(~lo + ~hi * shift64); + } + else + return (double)(lo + hi * shift64); + } + + }; + + //------------------------------------------------------------------------------ + //------------------------------------------------------------------------------ + + public struct IntPoint + { + public Int64 X; + public Int64 Y; + + public IntPoint(Int64 X, Int64 Y) + { + this.X = X; this.Y = Y; + } + + public IntPoint(IntPoint pt) + { + this.X = pt.X; this.Y = pt.Y; + } + } + + public struct IntRect + { + public Int64 left; + public Int64 top; + public Int64 right; + public Int64 bottom; + + public IntRect(Int64 l, Int64 t, Int64 r, Int64 b) + { + this.left = l; this.top = t; + this.right = r; this.bottom = b; + } + } + + public enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; + public enum PolyType { ptSubject, ptClip }; + //By far the most widely used winding rules for polygon filling are + //EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) + //Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) + //see http://glprogramming.com/red/chapter11.html + public enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; + public enum JoinType { jtSquare, jtRound, jtMiter }; + + + [Flags] + internal enum EdgeSide { esLeft = 1, esRight = 2 }; + [Flags] + internal enum Protects { ipNone = 0, ipLeft = 1, ipRight = 2, ipBoth = 3 }; + internal enum Direction { dRightToLeft, dLeftToRight }; + + internal class TEdge { + public Int64 xbot; + public Int64 ybot; + public Int64 xcurr; + public Int64 ycurr; + public Int64 xtop; + public Int64 ytop; + public double dx; + public Int64 deltaX; + public Int64 deltaY; + public Int64 tmpX; + public PolyType polyType; + public EdgeSide side; + public int windDelta; //1 or -1 depending on winding direction + public int windCnt; + public int windCnt2; //winding count of the opposite polytype + public int outIdx; + public TEdge next; + public TEdge prev; + public TEdge nextInLML; + public TEdge nextInAEL; + public TEdge prevInAEL; + public TEdge nextInSEL; + public TEdge prevInSEL; + }; + + internal class IntersectNode + { + public TEdge edge1; + public TEdge edge2; + public IntPoint pt; + public IntersectNode next; + }; + + internal class LocalMinima + { + public Int64 Y; + public TEdge leftBound; + public TEdge rightBound; + public LocalMinima next; + }; + + internal class Scanbeam + { + public Int64 Y; + public Scanbeam next; + }; + + internal class OutRec + { + public int idx; + public bool isHole; + public OutRec FirstLeft; //see comments in clipper.pas + public OutPt pts; + public OutPt bottomPt; + public PolyNode polyNode; + }; + + internal class OutPt + { + public int idx; + public IntPoint pt; + public OutPt next; + public OutPt prev; + }; + + internal class JoinRec + { + public IntPoint pt1a; + public IntPoint pt1b; + public int poly1Idx; + public IntPoint pt2a; + public IntPoint pt2b; + public int poly2Idx; + }; + + internal class HorzJoinRec + { + public TEdge edge; + public int savedIdx; + }; + + public class ClipperBase + { + protected const double horizontal = -3.4E+38; + internal const Int64 loRange = 0x3FFFFFFF; + internal const Int64 hiRange = 0x3FFFFFFFFFFFFFFFL; + + internal LocalMinima m_MinimaList; + internal LocalMinima m_CurrentLM; + internal List> m_edges = new List>(); + internal bool m_UseFullRange; + + //------------------------------------------------------------------------------ + + protected static bool PointsEqual(IntPoint pt1, IntPoint pt2) + { + return ( pt1.X == pt2.X && pt1.Y == pt2.Y ); + } + //------------------------------------------------------------------------------ + + internal bool PointIsVertex(IntPoint pt, OutPt pp) + { + OutPt pp2 = pp; + do + { + if (PointsEqual(pp2.pt, pt)) return true; + pp2 = pp2.next; + } + while (pp2 != pp); + return false; + } + //------------------------------------------------------------------------------ + + internal bool PointInPolygon(IntPoint pt, OutPt pp, bool UseFulllongRange) + { + OutPt pp2 = pp; + bool result = false; + if (UseFulllongRange) + { + do + { + if ((((pp2.pt.Y <= pt.Y) && (pt.Y < pp2.prev.pt.Y)) || + ((pp2.prev.pt.Y <= pt.Y) && (pt.Y < pp2.pt.Y))) && + new Int128(pt.X - pp2.pt.X) < + Int128.Int128Mul(pp2.prev.pt.X - pp2.pt.X, pt.Y - pp2.pt.Y) / + new Int128(pp2.prev.pt.Y - pp2.pt.Y)) + result = !result; + pp2 = pp2.next; + } + while (pp2 != pp); + } + else + { + do + { + if ((((pp2.pt.Y <= pt.Y) && (pt.Y < pp2.prev.pt.Y)) || + ((pp2.prev.pt.Y <= pt.Y) && (pt.Y < pp2.pt.Y))) && + (pt.X - pp2.pt.X < (pp2.prev.pt.X - pp2.pt.X) * (pt.Y - pp2.pt.Y) / + (pp2.prev.pt.Y - pp2.pt.Y))) result = !result; + pp2 = pp2.next; + } + while (pp2 != pp); + } + return result; + } + //------------------------------------------------------------------------------ + + internal bool SlopesEqual(TEdge e1, TEdge e2, bool UseFullRange) + { + if (UseFullRange) + return Int128.Int128Mul(e1.deltaY, e2.deltaX) == + Int128.Int128Mul(e1.deltaX, e2.deltaY); + else return (Int64)(e1.deltaY) * (e2.deltaX) == + (Int64)(e1.deltaX) * (e2.deltaY); + } + //------------------------------------------------------------------------------ + + protected bool SlopesEqual(IntPoint pt1, IntPoint pt2, + IntPoint pt3, bool UseFullRange) + { + if (UseFullRange) + return Int128.Int128Mul(pt1.Y - pt2.Y, pt2.X - pt3.X) == + Int128.Int128Mul(pt1.X - pt2.X, pt2.Y - pt3.Y); + else return + (Int64)(pt1.Y - pt2.Y) * (pt2.X - pt3.X) - (Int64)(pt1.X - pt2.X) * (pt2.Y - pt3.Y) == 0; + } + //------------------------------------------------------------------------------ + + protected bool SlopesEqual(IntPoint pt1, IntPoint pt2, + IntPoint pt3, IntPoint pt4, bool UseFullRange) + { + if (UseFullRange) + return Int128.Int128Mul(pt1.Y - pt2.Y, pt3.X - pt4.X) == + Int128.Int128Mul(pt1.X - pt2.X, pt3.Y - pt4.Y); + else return + (Int64)(pt1.Y - pt2.Y) * (pt3.X - pt4.X) - (Int64)(pt1.X - pt2.X) * (pt3.Y - pt4.Y) == 0; + } + //------------------------------------------------------------------------------ + + internal ClipperBase() //constructor (nb: no external instantiation) + { + m_MinimaList = null; + m_CurrentLM = null; + m_UseFullRange = false; + } + //------------------------------------------------------------------------------ + + //destructor - commented out since I gather this impedes the GC + //~ClipperBase() + //{ + // Clear(); + //} + //------------------------------------------------------------------------------ + + public virtual void Clear() + { + DisposeLocalMinimaList(); + for (int i = 0; i < m_edges.Count; ++i) + { + for (int j = 0; j < m_edges[i].Count; ++j) m_edges[i][j] = null; + m_edges[i].Clear(); + } + m_edges.Clear(); + m_UseFullRange = false; + } + //------------------------------------------------------------------------------ + + private void DisposeLocalMinimaList() + { + while( m_MinimaList != null ) + { + LocalMinima tmpLm = m_MinimaList.next; + m_MinimaList = null; + m_MinimaList = tmpLm; + } + m_CurrentLM = null; + } + //------------------------------------------------------------------------------ + + public bool AddPolygons(Polygons ppg, PolyType polyType) + { + bool result = false; + for (int i = 0; i < ppg.Count; ++i) + if (AddPolygon(ppg[i], polyType)) result = true; + return result; + } + //------------------------------------------------------------------------------ + + public bool AddPolygon(Polygon pg, PolyType polyType) + { + int len = pg.Count; + if (len < 3) return false; + Polygon p = new Polygon(len); + p.Add(new IntPoint(pg[0].X, pg[0].Y)); + int j = 0; + for (int i = 1; i < len; ++i) + { + + Int64 maxVal; + if (m_UseFullRange) maxVal = hiRange; else maxVal = loRange; + if (Math.Abs(pg[i].X) > maxVal || Math.Abs(pg[i].Y) > maxVal) + { + if (Math.Abs(pg[i].X) > hiRange || Math.Abs(pg[i].Y) > hiRange) + throw new ClipperException("Coordinate exceeds range bounds"); + maxVal = hiRange; + m_UseFullRange = true; + } + + if (PointsEqual(p[j], pg[i])) continue; + else if (j > 0 && SlopesEqual(p[j-1], p[j], pg[i], m_UseFullRange)) + { + if (PointsEqual(p[j-1], pg[i])) j--; + } else j++; + if (j < p.Count) + p[j] = pg[i]; else + p.Add(new IntPoint(pg[i].X, pg[i].Y)); + } + if (j < 2) return false; + + len = j+1; + while (len > 2) + { + //nb: test for point equality before testing slopes ... + if (PointsEqual(p[j], p[0])) j--; + else if (PointsEqual(p[0], p[1]) || SlopesEqual(p[j], p[0], p[1], m_UseFullRange)) + p[0] = p[j--]; + else if (SlopesEqual(p[j - 1], p[j], p[0], m_UseFullRange)) j--; + else if (SlopesEqual(p[0], p[1], p[2], m_UseFullRange)) + { + for (int i = 2; i <= j; ++i) p[i - 1] = p[i]; + j--; + } + else break; + len--; + } + if (len < 3) return false; + + //create a new edge array ... + List edges = new List(len); + for (int i = 0; i < len; i++) edges.Add(new TEdge()); + m_edges.Add(edges); + + //convert vertices to a double-linked-list of edges and initialize ... + edges[0].xcurr = p[0].X; + edges[0].ycurr = p[0].Y; + InitEdge(edges[len-1], edges[0], edges[len-2], p[len-1], polyType); + for (int i = len-2; i > 0; --i) + InitEdge(edges[i], edges[i+1], edges[i-1], p[i], polyType); + InitEdge(edges[0], edges[1], edges[len-1], p[0], polyType); + + //reset xcurr & ycurr and find 'eHighest' (given the Y axis coordinates + //increase downward so the 'highest' edge will have the smallest ytop) ... + TEdge e = edges[0]; + TEdge eHighest = e; + do + { + e.xcurr = e.xbot; + e.ycurr = e.ybot; + if (e.ytop < eHighest.ytop) eHighest = e; + e = e.next; + } + while ( e != edges[0]); + + //make sure eHighest is positioned so the following loop works safely ... + if (eHighest.windDelta > 0) eHighest = eHighest.next; + if (eHighest.dx == horizontal) eHighest = eHighest.next; + + //finally insert each local minima ... + e = eHighest; + do { + e = AddBoundsToLML(e); + } + while( e != eHighest ); + return true; + } + //------------------------------------------------------------------------------ + + private void InitEdge(TEdge e, TEdge eNext, + TEdge ePrev, IntPoint pt, PolyType polyType) + { + e.next = eNext; + e.prev = ePrev; + e.xcurr = pt.X; + e.ycurr = pt.Y; + if (e.ycurr >= e.next.ycurr) + { + e.xbot = e.xcurr; + e.ybot = e.ycurr; + e.xtop = e.next.xcurr; + e.ytop = e.next.ycurr; + e.windDelta = 1; + } else + { + e.xtop = e.xcurr; + e.ytop = e.ycurr; + e.xbot = e.next.xcurr; + e.ybot = e.next.ycurr; + e.windDelta = -1; + } + SetDx(e); + e.polyType = polyType; + e.outIdx = -1; + } + //------------------------------------------------------------------------------ + + private void SetDx(TEdge e) + { + e.deltaX = (e.xtop - e.xbot); + e.deltaY = (e.ytop - e.ybot); + if (e.deltaY == 0) e.dx = horizontal; + else e.dx = (double)(e.deltaX) / (e.deltaY); + } + //--------------------------------------------------------------------------- + + TEdge AddBoundsToLML(TEdge e) + { + //Starting at the top of one bound we progress to the bottom where there's + //a local minima. We then go to the top of the next bound. These two bounds + //form the left and right (or right and left) bounds of the local minima. + e.nextInLML = null; + e = e.next; + for (;;) + { + if ( e.dx == horizontal ) + { + //nb: proceed through horizontals when approaching from their right, + // but break on horizontal minima if approaching from their left. + // This ensures 'local minima' are always on the left of horizontals. + if (e.next.ytop < e.ytop && e.next.xbot > e.prev.xbot) break; + if (e.xtop != e.prev.xbot) SwapX(e); + e.nextInLML = e.prev; + } + else if (e.ycurr == e.prev.ycurr) break; + else e.nextInLML = e.prev; + e = e.next; + } + + //e and e.prev are now at a local minima ... + LocalMinima newLm = new LocalMinima(); + newLm.next = null; + newLm.Y = e.prev.ybot; + + if ( e.dx == horizontal ) //horizontal edges never start a left bound + { + if (e.xbot != e.prev.xbot) SwapX(e); + newLm.leftBound = e.prev; + newLm.rightBound = e; + } else if (e.dx < e.prev.dx) + { + newLm.leftBound = e.prev; + newLm.rightBound = e; + } else + { + newLm.leftBound = e; + newLm.rightBound = e.prev; + } + newLm.leftBound.side = EdgeSide.esLeft; + newLm.rightBound.side = EdgeSide.esRight; + InsertLocalMinima( newLm ); + + for (;;) + { + if ( e.next.ytop == e.ytop && e.next.dx != horizontal ) break; + e.nextInLML = e.next; + e = e.next; + if ( e.dx == horizontal && e.xbot != e.prev.xtop) SwapX(e); + } + return e.next; + } + //------------------------------------------------------------------------------ + + private void InsertLocalMinima(LocalMinima newLm) + { + if( m_MinimaList == null ) + { + m_MinimaList = newLm; + } + else if( newLm.Y >= m_MinimaList.Y ) + { + newLm.next = m_MinimaList; + m_MinimaList = newLm; + } else + { + LocalMinima tmpLm = m_MinimaList; + while( tmpLm.next != null && ( newLm.Y < tmpLm.next.Y ) ) + tmpLm = tmpLm.next; + newLm.next = tmpLm.next; + tmpLm.next = newLm; + } + } + //------------------------------------------------------------------------------ + + protected void PopLocalMinima() + { + if (m_CurrentLM == null) return; + m_CurrentLM = m_CurrentLM.next; + } + //------------------------------------------------------------------------------ + + private void SwapX(TEdge e) + { + //swap horizontal edges' top and bottom x's so they follow the natural + //progression of the bounds - ie so their xbots will align with the + //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] + e.xcurr = e.xtop; + e.xtop = e.xbot; + e.xbot = e.xcurr; + } + //------------------------------------------------------------------------------ + + protected virtual void Reset() + { + m_CurrentLM = m_MinimaList; + + //reset all edges ... + LocalMinima lm = m_MinimaList; + while (lm != null) + { + TEdge e = lm.leftBound; + while (e != null) + { + e.xcurr = e.xbot; + e.ycurr = e.ybot; + e.side = EdgeSide.esLeft; + e.outIdx = -1; + e = e.nextInLML; + } + e = lm.rightBound; + while (e != null) + { + e.xcurr = e.xbot; + e.ycurr = e.ybot; + e.side = EdgeSide.esRight; + e.outIdx = -1; + e = e.nextInLML; + } + lm = lm.next; + } + return; + } + //------------------------------------------------------------------------------ + + public IntRect GetBounds() + { + IntRect result = new IntRect(); + LocalMinima lm = m_MinimaList; + if (lm == null) return result; + result.left = lm.leftBound.xbot; + result.top = lm.leftBound.ybot; + result.right = lm.leftBound.xbot; + result.bottom = lm.leftBound.ybot; + while (lm != null) + { + if (lm.leftBound.ybot > result.bottom) + result.bottom = lm.leftBound.ybot; + TEdge e = lm.leftBound; + for (; ; ) + { + TEdge bottomE = e; + while (e.nextInLML != null) + { + if (e.xbot < result.left) result.left = e.xbot; + if (e.xbot > result.right) result.right = e.xbot; + e = e.nextInLML; + } + if (e.xbot < result.left) result.left = e.xbot; + if (e.xbot > result.right) result.right = e.xbot; + if (e.xtop < result.left) result.left = e.xtop; + if (e.xtop > result.right) result.right = e.xtop; + if (e.ytop < result.top) result.top = e.ytop; + + if (bottomE == lm.leftBound) e = lm.rightBound; + else break; + } + lm = lm.next; + } + return result; + } + + } //ClipperBase + + public class Clipper : ClipperBase + { + private List m_PolyOuts; + private ClipType m_ClipType; + private Scanbeam m_Scanbeam; + private TEdge m_ActiveEdges; + private TEdge m_SortedEdges; + private IntersectNode m_IntersectNodes; + private bool m_ExecuteLocked; + private PolyFillType m_ClipFillType; + private PolyFillType m_SubjFillType; + private List m_Joins; + private List m_HorizJoins; + private bool m_ReverseOutput; + private bool m_UsingPolyTree; + + public Clipper() + { + m_Scanbeam = null; + m_ActiveEdges = null; + m_SortedEdges = null; + m_IntersectNodes = null; + m_ExecuteLocked = false; + m_UsingPolyTree = false; + m_PolyOuts = new List(); + m_Joins = new List(); + m_HorizJoins = new List(); + m_ReverseOutput = false; + } + //------------------------------------------------------------------------------ + + //destructor - commented out since I gather this impedes the GC + //~Clipper() //destructor + //{ + // Clear(); + // DisposeScanbeamList(); + //} + //------------------------------------------------------------------------------ + + public override void Clear() + { + if (m_edges.Count == 0) return; //avoids problems with ClipperBase destructor + DisposeAllPolyPts(); + base.Clear(); + } + //------------------------------------------------------------------------------ + + void DisposeScanbeamList() + { + while ( m_Scanbeam != null ) { + Scanbeam sb2 = m_Scanbeam.next; + m_Scanbeam = null; + m_Scanbeam = sb2; + } + } + //------------------------------------------------------------------------------ + + protected override void Reset() + { + base.Reset(); + m_Scanbeam = null; + m_ActiveEdges = null; + m_SortedEdges = null; + DisposeAllPolyPts(); + LocalMinima lm = m_MinimaList; + while (lm != null) + { + InsertScanbeam(lm.Y); + InsertScanbeam(lm.leftBound.ytop); + lm = lm.next; + } + } + //------------------------------------------------------------------------------ + + public bool ReverseSolution + { + get { return m_ReverseOutput; } + set { m_ReverseOutput = value; } + } + //------------------------------------------------------------------------------ + + private void InsertScanbeam(Int64 Y) + { + if( m_Scanbeam == null ) + { + m_Scanbeam = new Scanbeam(); + m_Scanbeam.next = null; + m_Scanbeam.Y = Y; + } + else if( Y > m_Scanbeam.Y ) + { + Scanbeam newSb = new Scanbeam(); + newSb.Y = Y; + newSb.next = m_Scanbeam; + m_Scanbeam = newSb; + } else + { + Scanbeam sb2 = m_Scanbeam; + while( sb2.next != null && ( Y <= sb2.next.Y ) ) sb2 = sb2.next; + if( Y == sb2.Y ) return; //ie ignores duplicates + Scanbeam newSb = new Scanbeam(); + newSb.Y = Y; + newSb.next = sb2.next; + sb2.next = newSb; + } + } + //------------------------------------------------------------------------------ + + public bool Execute(ClipType clipType, Polygons solution, + PolyFillType subjFillType, PolyFillType clipFillType) + { + if (m_ExecuteLocked) return false; + m_ExecuteLocked = true; + solution.Clear(); + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = false; + bool succeeded = ExecuteInternal(); + //build the return polygons ... + if (succeeded) BuildResult(solution); + m_ExecuteLocked = false; + return succeeded; + } + //------------------------------------------------------------------------------ + + public bool Execute(ClipType clipType, PolyTree polytree, + PolyFillType subjFillType, PolyFillType clipFillType) + { + if (m_ExecuteLocked) return false; + m_ExecuteLocked = true; + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = true; + bool succeeded = ExecuteInternal(); + //build the return polygons ... + if (succeeded) BuildResult2(polytree); + m_ExecuteLocked = false; + return succeeded; + } + //------------------------------------------------------------------------------ + + public bool Execute(ClipType clipType, Polygons solution) + { + return Execute(clipType, solution, + PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); + } + //------------------------------------------------------------------------------ + + public bool Execute(ClipType clipType, PolyTree polytree) + { + return Execute(clipType, polytree, + PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); + } + //------------------------------------------------------------------------------ + + internal void FixHoleLinkage(OutRec outRec) + { + //skip if an outermost polygon or + //already already points to the correct FirstLeft ... + if (outRec.FirstLeft == null || + (outRec.isHole != outRec.FirstLeft.isHole && + outRec.FirstLeft.pts != null)) return; + + OutRec orfl = outRec.FirstLeft; + while (orfl != null && ((orfl.isHole == outRec.isHole) || orfl.pts == null)) + orfl = orfl.FirstLeft; + outRec.FirstLeft = orfl; + } + //------------------------------------------------------------------------------ + + private bool ExecuteInternal() + { + bool succeeded; + try + { + Reset(); + if (m_CurrentLM == null) return true; + Int64 botY = PopScanbeam(); + do + { + InsertLocalMinimaIntoAEL(botY); + m_HorizJoins.Clear(); + ProcessHorizontals(); + Int64 topY = PopScanbeam(); + succeeded = ProcessIntersections(botY, topY); + if (!succeeded) break; + ProcessEdgesAtTopOfScanbeam(topY); + botY = topY; + } while (m_Scanbeam != null); + } + catch { succeeded = false; } + + if (succeeded) + { + //tidy up output polygons and fix orientations where necessary ... + for (int i = 0; i < m_PolyOuts.Count; i++) + { + OutRec outRec = m_PolyOuts[i]; + if (outRec.pts == null) continue; + FixupOutPolygon(outRec); + if (outRec.pts == null) continue; + if ((outRec.isHole ^ m_ReverseOutput) == (Area(outRec, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec.pts); + } + JoinCommonEdges(); + } + m_Joins.Clear(); + m_HorizJoins.Clear(); + return succeeded; + } + //------------------------------------------------------------------------------ + + private Int64 PopScanbeam() + { + Int64 Y = m_Scanbeam.Y; + Scanbeam sb2 = m_Scanbeam; + m_Scanbeam = m_Scanbeam.next; + sb2 = null; + return Y; + } + //------------------------------------------------------------------------------ + + private void DisposeAllPolyPts(){ + for (int i = 0; i < m_PolyOuts.Count; ++i) DisposeOutRec(i); + m_PolyOuts.Clear(); + } + //------------------------------------------------------------------------------ + + void DisposeOutRec(int index) + { + OutRec outRec = m_PolyOuts[index]; + if (outRec.pts != null) DisposeOutPts(outRec.pts); + outRec = null; + m_PolyOuts[index] = null; + } + //------------------------------------------------------------------------------ + + private void DisposeOutPts(OutPt pp) + { + if (pp == null) return; + OutPt tmpPp = null; + pp.prev.next = null; + while (pp != null) + { + tmpPp = pp; + pp = pp.next; + tmpPp = null; + } + } + //------------------------------------------------------------------------------ + + private void AddJoin(TEdge e1, TEdge e2, int e1OutIdx, int e2OutIdx) + { + JoinRec jr = new JoinRec(); + if (e1OutIdx >= 0) + jr.poly1Idx = e1OutIdx; else + jr.poly1Idx = e1.outIdx; + jr.pt1a = new IntPoint(e1.xcurr, e1.ycurr); + jr.pt1b = new IntPoint(e1.xtop, e1.ytop); + if (e2OutIdx >= 0) + jr.poly2Idx = e2OutIdx; else + jr.poly2Idx = e2.outIdx; + jr.pt2a = new IntPoint(e2.xcurr, e2.ycurr); + jr.pt2b = new IntPoint(e2.xtop, e2.ytop); + m_Joins.Add(jr); + } + //------------------------------------------------------------------------------ + + private void AddHorzJoin(TEdge e, int idx) + { + HorzJoinRec hj = new HorzJoinRec(); + hj.edge = e; + hj.savedIdx = idx; + m_HorizJoins.Add(hj); + } + //------------------------------------------------------------------------------ + + private void InsertLocalMinimaIntoAEL(Int64 botY) + { + while( m_CurrentLM != null && ( m_CurrentLM.Y == botY ) ) + { + TEdge lb = m_CurrentLM.leftBound; + TEdge rb = m_CurrentLM.rightBound; + + InsertEdgeIntoAEL( lb ); + InsertScanbeam( lb.ytop ); + InsertEdgeIntoAEL( rb ); + + if (IsEvenOddFillType(lb)) + { + lb.windDelta = 1; + rb.windDelta = 1; + } + else + { + rb.windDelta = -lb.windDelta; + } + SetWindingCount(lb); + rb.windCnt = lb.windCnt; + rb.windCnt2 = lb.windCnt2; + + if( rb.dx == horizontal ) + { + //nb: only rightbounds can have a horizontal bottom edge + AddEdgeToSEL( rb ); + InsertScanbeam( rb.nextInLML.ytop ); + } + else + InsertScanbeam( rb.ytop ); + + if( IsContributing(lb) ) + AddLocalMinPoly(lb, rb, new IntPoint(lb.xcurr, m_CurrentLM.Y)); + + //if any output polygons share an edge, they'll need joining later ... + if (rb.outIdx >= 0 && rb.dx == horizontal) + { + for (int i = 0; i < m_HorizJoins.Count; i++) + { + IntPoint pt = new IntPoint(), pt2 = new IntPoint(); //used as dummy params. + HorzJoinRec hj = m_HorizJoins[i]; + //if horizontals rb and hj.edge overlap, flag for joining later ... + if (GetOverlapSegment(new IntPoint(hj.edge.xbot, hj.edge.ybot), + new IntPoint(hj.edge.xtop, hj.edge.ytop), + new IntPoint(rb.xbot, rb.ybot), + new IntPoint(rb.xtop, rb.ytop), + ref pt, ref pt2)) + AddJoin(hj.edge, rb, hj.savedIdx, -1); + } + } + + + if( lb.nextInAEL != rb ) + { + if (rb.outIdx >= 0 && rb.prevInAEL.outIdx >= 0 && + SlopesEqual(rb.prevInAEL, rb, m_UseFullRange)) + AddJoin(rb, rb.prevInAEL, -1, -1); + + TEdge e = lb.nextInAEL; + IntPoint pt = new IntPoint(lb.xcurr, lb.ycurr); + while( e != rb ) + { + if(e == null) + throw new ClipperException("InsertLocalMinimaIntoAEL: missing rightbound!"); + //nb: For calculating winding counts etc, IntersectEdges() assumes + //that param1 will be to the right of param2 ABOVE the intersection ... + IntersectEdges( rb , e , pt , Protects.ipNone); //order important here + e = e.nextInAEL; + } + } + PopLocalMinima(); + } + } + //------------------------------------------------------------------------------ + + private void InsertEdgeIntoAEL(TEdge edge) + { + edge.prevInAEL = null; + edge.nextInAEL = null; + if (m_ActiveEdges == null) + { + m_ActiveEdges = edge; + } + else if( E2InsertsBeforeE1(m_ActiveEdges, edge) ) + { + edge.nextInAEL = m_ActiveEdges; + m_ActiveEdges.prevInAEL = edge; + m_ActiveEdges = edge; + } else + { + TEdge e = m_ActiveEdges; + while (e.nextInAEL != null && !E2InsertsBeforeE1(e.nextInAEL, edge)) + e = e.nextInAEL; + edge.nextInAEL = e.nextInAEL; + if (e.nextInAEL != null) e.nextInAEL.prevInAEL = edge; + edge.prevInAEL = e; + e.nextInAEL = edge; + } + } + //---------------------------------------------------------------------- + + private bool E2InsertsBeforeE1(TEdge e1, TEdge e2) + { + return e2.xcurr == e1.xcurr? e2.dx > e1.dx : e2.xcurr < e1.xcurr; + } + //------------------------------------------------------------------------------ + + private bool IsEvenOddFillType(TEdge edge) + { + if (edge.polyType == PolyType.ptSubject) + return m_SubjFillType == PolyFillType.pftEvenOdd; + else + return m_ClipFillType == PolyFillType.pftEvenOdd; + } + //------------------------------------------------------------------------------ + + private bool IsEvenOddAltFillType(TEdge edge) + { + if (edge.polyType == PolyType.ptSubject) + return m_ClipFillType == PolyFillType.pftEvenOdd; + else + return m_SubjFillType == PolyFillType.pftEvenOdd; + } + //------------------------------------------------------------------------------ + + private bool IsContributing(TEdge edge) + { + PolyFillType pft, pft2; + if (edge.polyType == PolyType.ptSubject) + { + pft = m_SubjFillType; + pft2 = m_ClipFillType; + } + else + { + pft = m_ClipFillType; + pft2 = m_SubjFillType; + } + + switch (pft) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + if (Math.Abs(edge.windCnt) != 1) return false; + break; + case PolyFillType.pftPositive: + if (edge.windCnt != 1) return false; + break; + default: //PolyFillType.pftNegative + if (edge.windCnt != -1) return false; + break; + } + + switch (m_ClipType) + { + case ClipType.ctIntersection: + switch (pft2) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + return (edge.windCnt2 != 0); + case PolyFillType.pftPositive: + return (edge.windCnt2 > 0); + default: + return (edge.windCnt2 < 0); + } + case ClipType.ctUnion: + switch (pft2) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + return (edge.windCnt2 == 0); + case PolyFillType.pftPositive: + return (edge.windCnt2 <= 0); + default: + return (edge.windCnt2 >= 0); + } + case ClipType.ctDifference: + if (edge.polyType == PolyType.ptSubject) + switch (pft2) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + return (edge.windCnt2 == 0); + case PolyFillType.pftPositive: + return (edge.windCnt2 <= 0); + default: + return (edge.windCnt2 >= 0); + } + else + switch (pft2) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + return (edge.windCnt2 != 0); + case PolyFillType.pftPositive: + return (edge.windCnt2 > 0); + default: + return (edge.windCnt2 < 0); + } + } + return true; + } + //------------------------------------------------------------------------------ + + private void SetWindingCount(TEdge edge) + { + TEdge e = edge.prevInAEL; + //find the edge of the same polytype that immediately preceeds 'edge' in AEL + while (e != null && e.polyType != edge.polyType) + e = e.prevInAEL; + if (e == null) + { + edge.windCnt = edge.windDelta; + edge.windCnt2 = 0; + e = m_ActiveEdges; //ie get ready to calc windCnt2 + } + else if (IsEvenOddFillType(edge)) + { + //even-odd filling ... + edge.windCnt = 1; + edge.windCnt2 = e.windCnt2; + e = e.nextInAEL; //ie get ready to calc windCnt2 + } + else + { + //nonZero filling ... + if (e.windCnt * e.windDelta < 0) + { + if (Math.Abs(e.windCnt) > 1) + { + if (e.windDelta * edge.windDelta < 0) + edge.windCnt = e.windCnt; + else + edge.windCnt = e.windCnt + edge.windDelta; + } + else + edge.windCnt = e.windCnt + e.windDelta + edge.windDelta; + } + else + { + if (Math.Abs(e.windCnt) > 1 && e.windDelta * edge.windDelta < 0) + edge.windCnt = e.windCnt; + else if (e.windCnt + edge.windDelta == 0) + edge.windCnt = e.windCnt; + else + edge.windCnt = e.windCnt + edge.windDelta; + } + edge.windCnt2 = e.windCnt2; + e = e.nextInAEL; //ie get ready to calc windCnt2 + } + + //update windCnt2 ... + if (IsEvenOddAltFillType(edge)) + { + //even-odd filling ... + while (e != edge) + { + edge.windCnt2 = (edge.windCnt2 == 0) ? 1 : 0; + e = e.nextInAEL; + } + } + else + { + //nonZero filling ... + while (e != edge) + { + edge.windCnt2 += e.windDelta; + e = e.nextInAEL; + } + } + } + //------------------------------------------------------------------------------ + + private void AddEdgeToSEL(TEdge edge) + { + //SEL pointers in PEdge are reused to build a list of horizontal edges. + //However, we don't need to worry about order with horizontal edge processing. + if (m_SortedEdges == null) + { + m_SortedEdges = edge; + edge.prevInSEL = null; + edge.nextInSEL = null; + } + else + { + edge.nextInSEL = m_SortedEdges; + edge.prevInSEL = null; + m_SortedEdges.prevInSEL = edge; + m_SortedEdges = edge; + } + } + //------------------------------------------------------------------------------ + + private void CopyAELToSEL() + { + TEdge e = m_ActiveEdges; + m_SortedEdges = e; + while (e != null) + { + e.prevInSEL = e.prevInAEL; + e.nextInSEL = e.nextInAEL; + e = e.nextInAEL; + } + } + //------------------------------------------------------------------------------ + + private void SwapPositionsInAEL(TEdge edge1, TEdge edge2) + { + if (edge1.nextInAEL == edge2) + { + TEdge next = edge2.nextInAEL; + if (next != null) + next.prevInAEL = edge1; + TEdge prev = edge1.prevInAEL; + if (prev != null) + prev.nextInAEL = edge2; + edge2.prevInAEL = prev; + edge2.nextInAEL = edge1; + edge1.prevInAEL = edge2; + edge1.nextInAEL = next; + } + else if (edge2.nextInAEL == edge1) + { + TEdge next = edge1.nextInAEL; + if (next != null) + next.prevInAEL = edge2; + TEdge prev = edge2.prevInAEL; + if (prev != null) + prev.nextInAEL = edge1; + edge1.prevInAEL = prev; + edge1.nextInAEL = edge2; + edge2.prevInAEL = edge1; + edge2.nextInAEL = next; + } + else + { + TEdge next = edge1.nextInAEL; + TEdge prev = edge1.prevInAEL; + edge1.nextInAEL = edge2.nextInAEL; + if (edge1.nextInAEL != null) + edge1.nextInAEL.prevInAEL = edge1; + edge1.prevInAEL = edge2.prevInAEL; + if (edge1.prevInAEL != null) + edge1.prevInAEL.nextInAEL = edge1; + edge2.nextInAEL = next; + if (edge2.nextInAEL != null) + edge2.nextInAEL.prevInAEL = edge2; + edge2.prevInAEL = prev; + if (edge2.prevInAEL != null) + edge2.prevInAEL.nextInAEL = edge2; + } + + if (edge1.prevInAEL == null) + m_ActiveEdges = edge1; + else if (edge2.prevInAEL == null) + m_ActiveEdges = edge2; + } + //------------------------------------------------------------------------------ + + private void SwapPositionsInSEL(TEdge edge1, TEdge edge2) + { + if (edge1.nextInSEL == null && edge1.prevInSEL == null) + return; + if (edge2.nextInSEL == null && edge2.prevInSEL == null) + return; + + if (edge1.nextInSEL == edge2) + { + TEdge next = edge2.nextInSEL; + if (next != null) + next.prevInSEL = edge1; + TEdge prev = edge1.prevInSEL; + if (prev != null) + prev.nextInSEL = edge2; + edge2.prevInSEL = prev; + edge2.nextInSEL = edge1; + edge1.prevInSEL = edge2; + edge1.nextInSEL = next; + } + else if (edge2.nextInSEL == edge1) + { + TEdge next = edge1.nextInSEL; + if (next != null) + next.prevInSEL = edge2; + TEdge prev = edge2.prevInSEL; + if (prev != null) + prev.nextInSEL = edge1; + edge1.prevInSEL = prev; + edge1.nextInSEL = edge2; + edge2.prevInSEL = edge1; + edge2.nextInSEL = next; + } + else + { + TEdge next = edge1.nextInSEL; + TEdge prev = edge1.prevInSEL; + edge1.nextInSEL = edge2.nextInSEL; + if (edge1.nextInSEL != null) + edge1.nextInSEL.prevInSEL = edge1; + edge1.prevInSEL = edge2.prevInSEL; + if (edge1.prevInSEL != null) + edge1.prevInSEL.nextInSEL = edge1; + edge2.nextInSEL = next; + if (edge2.nextInSEL != null) + edge2.nextInSEL.prevInSEL = edge2; + edge2.prevInSEL = prev; + if (edge2.prevInSEL != null) + edge2.prevInSEL.nextInSEL = edge2; + } + + if (edge1.prevInSEL == null) + m_SortedEdges = edge1; + else if (edge2.prevInSEL == null) + m_SortedEdges = edge2; + } + //------------------------------------------------------------------------------ + + + private void AddLocalMaxPoly(TEdge e1, TEdge e2, IntPoint pt) + { + AddOutPt(e1, pt); + if (e1.outIdx == e2.outIdx) + { + e1.outIdx = -1; + e2.outIdx = -1; + } + else if (e1.outIdx < e2.outIdx) + AppendPolygon(e1, e2); + else + AppendPolygon(e2, e1); + } + //------------------------------------------------------------------------------ + + private void AddLocalMinPoly(TEdge e1, TEdge e2, IntPoint pt) + { + TEdge e, prevE; + if (e2.dx == horizontal || (e1.dx > e2.dx)) + { + AddOutPt(e1, pt); + e2.outIdx = e1.outIdx; + e1.side = EdgeSide.esLeft; + e2.side = EdgeSide.esRight; + e = e1; + if (e.prevInAEL == e2) + prevE = e2.prevInAEL; + else + prevE = e.prevInAEL; + } + else + { + AddOutPt(e2, pt); + e1.outIdx = e2.outIdx; + e1.side = EdgeSide.esRight; + e2.side = EdgeSide.esLeft; + e = e2; + if (e.prevInAEL == e1) + prevE = e1.prevInAEL; + else + prevE = e.prevInAEL; + } + + if (prevE != null && prevE.outIdx >= 0 && + (TopX(prevE, pt.Y) == TopX(e, pt.Y)) && + SlopesEqual(e, prevE, m_UseFullRange)) + AddJoin(e, prevE, -1, -1); + + } + //------------------------------------------------------------------------------ + + private OutRec CreateOutRec() + { + OutRec result = new OutRec(); + result.idx = -1; + result.isHole = false; + result.FirstLeft = null; + result.pts = null; + result.bottomPt = null; + result.polyNode = null; + return result; + } + //------------------------------------------------------------------------------ + + private void AddOutPt(TEdge e, IntPoint pt) + { + bool ToFront = (e.side == EdgeSide.esLeft); + if( e.outIdx < 0 ) + { + OutRec outRec = CreateOutRec(); + m_PolyOuts.Add(outRec); + outRec.idx = m_PolyOuts.Count -1; + e.outIdx = outRec.idx; + OutPt op = new OutPt(); + outRec.pts = op; + outRec.bottomPt = op; + op.pt = pt; + op.idx = outRec.idx; + op.next = op; + op.prev = op; + SetHoleState(e, outRec); + } else + { + OutRec outRec = m_PolyOuts[e.outIdx]; + OutPt op = outRec.pts, op2; + if (ToFront && PointsEqual(pt, op.pt) || + (!ToFront && PointsEqual(pt, op.prev.pt))) return; + + op2 = new OutPt(); + op2.pt = pt; + op2.idx = outRec.idx; + if (op2.pt.Y == outRec.bottomPt.pt.Y && + op2.pt.X < outRec.bottomPt.pt.X) + outRec.bottomPt = op2; + op2.next = op; + op2.prev = op.prev; + op2.prev.next = op2; + op.prev = op2; + if (ToFront) outRec.pts = op2; + } + } + //------------------------------------------------------------------------------ + + internal void SwapPoints(ref IntPoint pt1, ref IntPoint pt2) + { + IntPoint tmp = pt1; + pt1 = pt2; + pt2 = tmp; + } + //------------------------------------------------------------------------------ + + private bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, + IntPoint pt2b, ref IntPoint pt1, ref IntPoint pt2) + { + //precondition: segments are colinear. + if (Math.Abs(pt1a.X - pt1b.X) > Math.Abs(pt1a.Y - pt1b.Y)) + { + if (pt1a.X > pt1b.X) SwapPoints(ref pt1a, ref pt1b); + if (pt2a.X > pt2b.X) SwapPoints(ref pt2a, ref pt2b); + if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; + return pt1.X < pt2.X; + } else + { + if (pt1a.Y < pt1b.Y) SwapPoints(ref pt1a, ref pt1b); + if (pt2a.Y < pt2b.Y) SwapPoints(ref pt2a, ref pt2b); + if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; + return pt1.Y > pt2.Y; + } + } + //------------------------------------------------------------------------------ + + private bool FindSegment(ref OutPt pp, ref IntPoint pt1, ref IntPoint pt2) + { + if (pp == null) return false; + OutPt pp2 = pp; + IntPoint pt1a = new IntPoint(pt1); + IntPoint pt2a = new IntPoint(pt2); + do + { + if (SlopesEqual(pt1a, pt2a, pp.pt, pp.prev.pt, true) && + SlopesEqual(pt1a, pt2a, pp.pt, true) && + GetOverlapSegment(pt1a, pt2a, pp.pt, pp.prev.pt, ref pt1, ref pt2)) + return true; + pp = pp.next; + } + while (pp != pp2); + return false; + } + //------------------------------------------------------------------------------ + + internal bool Pt3IsBetweenPt1AndPt2(IntPoint pt1, IntPoint pt2, IntPoint pt3) + { + if (PointsEqual(pt1, pt3) || PointsEqual(pt2, pt3)) return true; + else if (pt1.X != pt2.X) return (pt1.X < pt3.X) == (pt3.X < pt2.X); + else return (pt1.Y < pt3.Y) == (pt3.Y < pt2.Y); + } + //------------------------------------------------------------------------------ + + private OutPt InsertPolyPtBetween(OutPt p1, OutPt p2, IntPoint pt) + { + OutPt result = new OutPt(); + result.pt = pt; + if (p2 == p1.next) + { + p1.next = result; + p2.prev = result; + result.next = p2; + result.prev = p1; + } else + { + p2.next = result; + p1.prev = result; + result.next = p1; + result.prev = p2; + } + return result; + } + //------------------------------------------------------------------------------ + + private void SetHoleState(TEdge e, OutRec outRec) + { + bool isHole = false; + TEdge e2 = e.prevInAEL; + while (e2 != null) + { + if (e2.outIdx >= 0) + { + isHole = !isHole; + if (outRec.FirstLeft == null) + outRec.FirstLeft = m_PolyOuts[e2.outIdx]; + } + e2 = e2.prevInAEL; + } + if (isHole) outRec.isHole = true; + } + //------------------------------------------------------------------------------ + + private double GetDx(IntPoint pt1, IntPoint pt2) + { + if (pt1.Y == pt2.Y) return horizontal; + else return (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); + } + //--------------------------------------------------------------------------- + + private bool FirstIsBottomPt(OutPt btmPt1, OutPt btmPt2) + { + OutPt p = btmPt1.prev; + while (PointsEqual(p.pt, btmPt1.pt) && (p != btmPt1)) p = p.prev; + double dx1p = Math.Abs(GetDx(btmPt1.pt, p.pt)); + p = btmPt1.next; + while (PointsEqual(p.pt, btmPt1.pt) && (p != btmPt1)) p = p.next; + double dx1n = Math.Abs(GetDx(btmPt1.pt, p.pt)); + + p = btmPt2.prev; + while (PointsEqual(p.pt, btmPt2.pt) && (p != btmPt2)) p = p.prev; + double dx2p = Math.Abs(GetDx(btmPt2.pt, p.pt)); + p = btmPt2.next; + while (PointsEqual(p.pt, btmPt2.pt) && (p != btmPt2)) p = p.next; + double dx2n = Math.Abs(GetDx(btmPt2.pt, p.pt)); + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); + } + //------------------------------------------------------------------------------ + + private OutPt GetBottomPt(OutPt pp) + { + OutPt dups = null; + OutPt p = pp.next; + while (p != pp) + { + if (p.pt.Y > pp.pt.Y) + { + pp = p; + dups = null; + } + else if (p.pt.Y == pp.pt.Y && p.pt.X <= pp.pt.X) + { + if (p.pt.X < pp.pt.X) + { + dups = null; + pp = p; + } else + { + if (p.next != pp && p.prev != pp) dups = p; + } + } + p = p.next; + } + if (dups != null) + { + //there appears to be at least 2 vertices at bottomPt so ... + while (dups != p) + { + if (!FirstIsBottomPt(p, dups)) pp = dups; + dups = dups.next; + while (!PointsEqual(dups.pt, pp.pt)) dups = dups.next; + } + } + return pp; + } + //------------------------------------------------------------------------------ + + private OutRec GetLowermostRec(OutRec outRec1, OutRec outRec2) + { + //work out which polygon fragment has the correct hole state ... + OutPt bPt1 = outRec1.bottomPt; + OutPt bPt2 = outRec2.bottomPt; + if (bPt1.pt.Y > bPt2.pt.Y) return outRec1; + else if (bPt1.pt.Y < bPt2.pt.Y) return outRec2; + else if (bPt1.pt.X < bPt2.pt.X) return outRec1; + else if (bPt1.pt.X > bPt2.pt.X) return outRec2; + else if (bPt1.next == bPt1) return outRec2; + else if (bPt2.next == bPt2) return outRec1; + else if (FirstIsBottomPt(bPt1, bPt2)) return outRec1; + else return outRec2; + } + //------------------------------------------------------------------------------ + + bool Param1RightOfParam2(OutRec outRec1, OutRec outRec2) + { + do + { + outRec1 = outRec1.FirstLeft; + if (outRec1 == outRec2) return true; + } while (outRec1 != null); + return false; + } + //------------------------------------------------------------------------------ + + private void AppendPolygon(TEdge e1, TEdge e2) + { + //get the start and ends of both output polygons ... + OutRec outRec1 = m_PolyOuts[e1.outIdx]; + OutRec outRec2 = m_PolyOuts[e2.outIdx]; + + OutRec holeStateRec; + if (Param1RightOfParam2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + OutPt p1_lft = outRec1.pts; + OutPt p1_rt = p1_lft.prev; + OutPt p2_lft = outRec2.pts; + OutPt p2_rt = p2_lft.prev; + + EdgeSide side; + //join e2 poly onto e1 poly and delete pointers to e2 ... + if( e1.side == EdgeSide.esLeft ) + { + if (e2.side == EdgeSide.esLeft) + { + //z y x a b c + ReversePolyPtLinks(p2_lft); + p2_lft.next = p1_lft; + p1_lft.prev = p2_lft; + p1_rt.next = p2_rt; + p2_rt.prev = p1_rt; + outRec1.pts = p2_rt; + } else + { + //x y z a b c + p2_rt.next = p1_lft; + p1_lft.prev = p2_rt; + p2_lft.prev = p1_rt; + p1_rt.next = p2_lft; + outRec1.pts = p2_lft; + } + side = EdgeSide.esLeft; + } else + { + if (e2.side == EdgeSide.esRight) + { + //a b c z y x + ReversePolyPtLinks( p2_lft ); + p1_rt.next = p2_rt; + p2_rt.prev = p1_rt; + p2_lft.next = p1_lft; + p1_lft.prev = p2_lft; + } else + { + //a b c x y z + p1_rt.next = p2_lft; + p2_lft.prev = p1_rt; + p1_lft.prev = p2_rt; + p2_rt.next = p1_lft; + } + side = EdgeSide.esRight; + } + + if (holeStateRec == outRec2) + { + outRec1.bottomPt = outRec2.bottomPt; + outRec1.bottomPt.idx = outRec1.idx; + if (outRec2.FirstLeft != outRec1) + outRec1.FirstLeft = outRec2.FirstLeft; + outRec1.isHole = outRec2.isHole; + } + outRec2.pts = null; + outRec2.bottomPt = null; + + outRec2.FirstLeft = outRec1; + + int OKIdx = e1.outIdx; + int ObsoleteIdx = e2.outIdx; + + e1.outIdx = -1; //nb: safe because we only get here via AddLocalMaxPoly + e2.outIdx = -1; + + TEdge e = m_ActiveEdges; + while( e != null ) + { + if( e.outIdx == ObsoleteIdx ) + { + e.outIdx = OKIdx; + e.side = side; + break; + } + e = e.nextInAEL; + } + + + for (int i = 0; i < m_Joins.Count; ++i) + { + if (m_Joins[i].poly1Idx == ObsoleteIdx) m_Joins[i].poly1Idx = OKIdx; + if (m_Joins[i].poly2Idx == ObsoleteIdx) m_Joins[i].poly2Idx = OKIdx; + } + + for (int i = 0; i < m_HorizJoins.Count; ++i) + { + if (m_HorizJoins[i].savedIdx == ObsoleteIdx) + m_HorizJoins[i].savedIdx = OKIdx; + } + + } + //------------------------------------------------------------------------------ + + private void ReversePolyPtLinks(OutPt pp) + { + if (pp == null) return; + OutPt pp1; + OutPt pp2; + pp1 = pp; + do + { + pp2 = pp1.next; + pp1.next = pp1.prev; + pp1.prev = pp2; + pp1 = pp2; + } while (pp1 != pp); + } + //------------------------------------------------------------------------------ + + private static void SwapSides(TEdge edge1, TEdge edge2) + { + EdgeSide side = edge1.side; + edge1.side = edge2.side; + edge2.side = side; + } + //------------------------------------------------------------------------------ + + private static void SwapPolyIndexes(TEdge edge1, TEdge edge2) + { + int outIdx = edge1.outIdx; + edge1.outIdx = edge2.outIdx; + edge2.outIdx = outIdx; + } + //------------------------------------------------------------------------------ + + private void DoEdge1(TEdge edge1, TEdge edge2, IntPoint pt) + { + AddOutPt(edge1, pt); + SwapSides(edge1, edge2); + SwapPolyIndexes(edge1, edge2); + } + //------------------------------------------------------------------------------ + + private void DoEdge2(TEdge edge1, TEdge edge2, IntPoint pt) + { + AddOutPt(edge2, pt); + SwapSides(edge1, edge2); + SwapPolyIndexes(edge1, edge2); + } + //------------------------------------------------------------------------------ + + private void DoBothEdges(TEdge edge1, TEdge edge2, IntPoint pt) + { + AddOutPt(edge1, pt); + AddOutPt(edge2, pt); + SwapSides(edge1, edge2); + SwapPolyIndexes(edge1, edge2); + } + //------------------------------------------------------------------------------ + + private void IntersectEdges(TEdge e1, TEdge e2, IntPoint pt, Protects protects) + { + //e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before + //e2 in AEL except when e1 is being inserted at the intersection point ... + + bool e1stops = (Protects.ipLeft & protects) == 0 && e1.nextInLML == null && + e1.xtop == pt.X && e1.ytop == pt.Y; + bool e2stops = (Protects.ipRight & protects) == 0 && e2.nextInLML == null && + e2.xtop == pt.X && e2.ytop == pt.Y; + bool e1Contributing = (e1.outIdx >= 0); + bool e2contributing = (e2.outIdx >= 0); + + //update winding counts... + //assumes that e1 will be to the right of e2 ABOVE the intersection + if (e1.polyType == e2.polyType) + { + if (IsEvenOddFillType(e1)) + { + int oldE1WindCnt = e1.windCnt; + e1.windCnt = e2.windCnt; + e2.windCnt = oldE1WindCnt; + } + else + { + if (e1.windCnt + e2.windDelta == 0) e1.windCnt = -e1.windCnt; + else e1.windCnt += e2.windDelta; + if (e2.windCnt - e1.windDelta == 0) e2.windCnt = -e2.windCnt; + else e2.windCnt -= e1.windDelta; + } + } + else + { + if (!IsEvenOddFillType(e2)) e1.windCnt2 += e2.windDelta; + else e1.windCnt2 = (e1.windCnt2 == 0) ? 1 : 0; + if (!IsEvenOddFillType(e1)) e2.windCnt2 -= e1.windDelta; + else e2.windCnt2 = (e2.windCnt2 == 0) ? 1 : 0; + } + + PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; + if (e1.polyType == PolyType.ptSubject) + { + e1FillType = m_SubjFillType; + e1FillType2 = m_ClipFillType; + } + else + { + e1FillType = m_ClipFillType; + e1FillType2 = m_SubjFillType; + } + if (e2.polyType == PolyType.ptSubject) + { + e2FillType = m_SubjFillType; + e2FillType2 = m_ClipFillType; + } + else + { + e2FillType = m_ClipFillType; + e2FillType2 = m_SubjFillType; + } + + int e1Wc, e2Wc; + switch (e1FillType) + { + case PolyFillType.pftPositive: e1Wc = e1.windCnt; break; + case PolyFillType.pftNegative: e1Wc = -e1.windCnt; break; + default: e1Wc = Math.Abs(e1.windCnt); break; + } + switch (e2FillType) + { + case PolyFillType.pftPositive: e2Wc = e2.windCnt; break; + case PolyFillType.pftNegative: e2Wc = -e2.windCnt; break; + default: e2Wc = Math.Abs(e2.windCnt); break; + } + + + if (e1Contributing && e2contributing) + { + if ( e1stops || e2stops || + (e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + (e1.polyType != e2.polyType && m_ClipType != ClipType.ctXor)) + AddLocalMaxPoly(e1, e2, pt); + else + DoBothEdges(e1, e2, pt); + } + else if (e1Contributing) + { + if ((e2Wc == 0 || e2Wc == 1) && + (m_ClipType != ClipType.ctIntersection || + e2.polyType == PolyType.ptSubject || (e2.windCnt2 != 0))) + DoEdge1(e1, e2, pt); + } + else if (e2contributing) + { + if ((e1Wc == 0 || e1Wc == 1) && + (m_ClipType != ClipType.ctIntersection || + e1.polyType == PolyType.ptSubject || (e1.windCnt2 != 0))) + DoEdge2(e1, e2, pt); + } + else if ( (e1Wc == 0 || e1Wc == 1) && + (e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops ) + { + //neither edge is currently contributing ... + Int64 e1Wc2, e2Wc2; + switch (e1FillType2) + { + case PolyFillType.pftPositive: e1Wc2 = e1.windCnt2; break; + case PolyFillType.pftNegative: e1Wc2 = -e1.windCnt2; break; + default: e1Wc2 = Math.Abs(e1.windCnt2); break; + } + switch (e2FillType2) + { + case PolyFillType.pftPositive: e2Wc2 = e2.windCnt2; break; + case PolyFillType.pftNegative: e2Wc2 = -e2.windCnt2; break; + default: e2Wc2 = Math.Abs(e2.windCnt2); break; + } + + if (e1.polyType != e2.polyType) + AddLocalMinPoly(e1, e2, pt); + else if (e1Wc == 1 && e2Wc == 1) + switch (m_ClipType) + { + case ClipType.ctIntersection: + { + if (e1Wc2 > 0 && e2Wc2 > 0) + AddLocalMinPoly(e1, e2, pt); + break; + } + case ClipType.ctUnion: + { + if (e1Wc2 <= 0 && e2Wc2 <= 0) + AddLocalMinPoly(e1, e2, pt); + break; + } + case ClipType.ctDifference: + { + if (((e1.polyType == PolyType.ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((e1.polyType == PolyType.ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + AddLocalMinPoly(e1, e2, pt); + break; + } + case ClipType.ctXor: + { + AddLocalMinPoly(e1, e2, pt); + break; + } + } + else + SwapSides(e1, e2); + } + + if ((e1stops != e2stops) && + ((e1stops && (e1.outIdx >= 0)) || (e2stops && (e2.outIdx >= 0)))) + { + SwapSides(e1, e2); + SwapPolyIndexes(e1, e2); + } + + //finally, delete any non-contributing maxima edges ... + if (e1stops) DeleteFromAEL(e1); + if (e2stops) DeleteFromAEL(e2); + } + //------------------------------------------------------------------------------ + + private void DeleteFromAEL(TEdge e) + { + TEdge AelPrev = e.prevInAEL; + TEdge AelNext = e.nextInAEL; + if (AelPrev == null && AelNext == null && (e != m_ActiveEdges)) + return; //already deleted + if (AelPrev != null) + AelPrev.nextInAEL = AelNext; + else m_ActiveEdges = AelNext; + if (AelNext != null) + AelNext.prevInAEL = AelPrev; + e.nextInAEL = null; + e.prevInAEL = null; + } + //------------------------------------------------------------------------------ + + private void DeleteFromSEL(TEdge e) + { + TEdge SelPrev = e.prevInSEL; + TEdge SelNext = e.nextInSEL; + if (SelPrev == null && SelNext == null && (e != m_SortedEdges)) + return; //already deleted + if (SelPrev != null) + SelPrev.nextInSEL = SelNext; + else m_SortedEdges = SelNext; + if (SelNext != null) + SelNext.prevInSEL = SelPrev; + e.nextInSEL = null; + e.prevInSEL = null; + } + //------------------------------------------------------------------------------ + + private void UpdateEdgeIntoAEL(ref TEdge e) + { + if (e.nextInLML == null) + throw new ClipperException("UpdateEdgeIntoAEL: invalid call"); + TEdge AelPrev = e.prevInAEL; + TEdge AelNext = e.nextInAEL; + e.nextInLML.outIdx = e.outIdx; + if (AelPrev != null) + AelPrev.nextInAEL = e.nextInLML; + else m_ActiveEdges = e.nextInLML; + if (AelNext != null) + AelNext.prevInAEL = e.nextInLML; + e.nextInLML.side = e.side; + e.nextInLML.windDelta = e.windDelta; + e.nextInLML.windCnt = e.windCnt; + e.nextInLML.windCnt2 = e.windCnt2; + e = e.nextInLML; + e.prevInAEL = AelPrev; + e.nextInAEL = AelNext; + if (e.dx != horizontal) InsertScanbeam(e.ytop); + } + //------------------------------------------------------------------------------ + + private void ProcessHorizontals() + { + TEdge horzEdge = m_SortedEdges; + while (horzEdge != null) + { + DeleteFromSEL(horzEdge); + ProcessHorizontal(horzEdge); + horzEdge = m_SortedEdges; + } + } + //------------------------------------------------------------------------------ + + private void ProcessHorizontal(TEdge horzEdge) + { + Direction Direction; + Int64 horzLeft, horzRight; + + if (horzEdge.xcurr < horzEdge.xtop) + { + horzLeft = horzEdge.xcurr; + horzRight = horzEdge.xtop; + Direction = Direction.dLeftToRight; + } + else + { + horzLeft = horzEdge.xtop; + horzRight = horzEdge.xcurr; + Direction = Direction.dRightToLeft; + } + + TEdge eMaxPair; + if (horzEdge.nextInLML != null) + eMaxPair = null; + else + eMaxPair = GetMaximaPair(horzEdge); + + TEdge e = GetNextInAEL(horzEdge, Direction); + while (e != null) + { + TEdge eNext = GetNextInAEL(e, Direction); + if (eMaxPair != null || + ((Direction == Direction.dLeftToRight) && (e.xcurr <= horzRight)) || + ((Direction == Direction.dRightToLeft) && (e.xcurr >= horzLeft))) + { + //ok, so far it looks like we're still in range of the horizontal edge + if (e.xcurr == horzEdge.xtop && eMaxPair == null) + { + if (SlopesEqual(e, horzEdge.nextInLML, m_UseFullRange)) + { + //if output polygons share an edge, they'll need joining later ... + if (horzEdge.outIdx >= 0 && e.outIdx >= 0) + AddJoin(horzEdge.nextInLML, e, horzEdge.outIdx, -1); + break; //we've reached the end of the horizontal line + } + else if (e.dx < horzEdge.nextInLML.dx) + //we really have got to the end of the intermediate horz edge so quit. + //nb: More -ve slopes follow more +ve slopes ABOVE the horizontal. + break; + } + + if (e == eMaxPair) + { + //horzEdge is evidently a maxima horizontal and we've arrived at its end. + if (Direction == Direction.dLeftToRight) + IntersectEdges(horzEdge, e, new IntPoint(e.xcurr, horzEdge.ycurr), 0); + else + IntersectEdges(e, horzEdge, new IntPoint(e.xcurr, horzEdge.ycurr), 0); + if (eMaxPair.outIdx >= 0) throw new ClipperException("ProcessHorizontal error"); + return; + } + else if (e.dx == horizontal && !IsMinima(e) && !(e.xcurr > e.xtop)) + { + if (Direction == Direction.dLeftToRight) + IntersectEdges(horzEdge, e, new IntPoint(e.xcurr, horzEdge.ycurr), + (IsTopHorz(horzEdge, e.xcurr)) ? Protects.ipLeft : Protects.ipBoth); + else + IntersectEdges(e, horzEdge, new IntPoint(e.xcurr, horzEdge.ycurr), + (IsTopHorz(horzEdge, e.xcurr)) ? Protects.ipRight : Protects.ipBoth); + } + else if (Direction == Direction.dLeftToRight) + { + IntersectEdges(horzEdge, e, new IntPoint(e.xcurr, horzEdge.ycurr), + (IsTopHorz(horzEdge, e.xcurr)) ? Protects.ipLeft : Protects.ipBoth); + } + else + { + IntersectEdges(e, horzEdge, new IntPoint(e.xcurr, horzEdge.ycurr), + (IsTopHorz(horzEdge, e.xcurr)) ? Protects.ipRight : Protects.ipBoth); + } + SwapPositionsInAEL(horzEdge, e); + } + else if ( (Direction == Direction.dLeftToRight && + e.xcurr > horzRight && horzEdge.nextInSEL == null) || + (Direction == Direction.dRightToLeft && + e.xcurr < horzLeft && horzEdge.nextInSEL == null) ) break; + e = eNext; + } //end while ( e ) + + if (horzEdge.nextInLML != null) + { + if (horzEdge.outIdx >= 0) + AddOutPt(horzEdge, new IntPoint(horzEdge.xtop, horzEdge.ytop)); + UpdateEdgeIntoAEL(ref horzEdge); + } + else + { + if (horzEdge.outIdx >= 0) + IntersectEdges(horzEdge, eMaxPair, + new IntPoint(horzEdge.xtop, horzEdge.ycurr), Protects.ipBoth); + DeleteFromAEL(eMaxPair); + DeleteFromAEL(horzEdge); + } + } + //------------------------------------------------------------------------------ + + private bool IsTopHorz(TEdge horzEdge, double XPos) + { + TEdge e = m_SortedEdges; + while (e != null) + { + if ((XPos >= Math.Min(e.xcurr, e.xtop)) && (XPos <= Math.Max(e.xcurr, e.xtop))) + return false; + e = e.nextInSEL; + } + return true; + } + //------------------------------------------------------------------------------ + + private TEdge GetNextInAEL(TEdge e, Direction Direction) + { + return Direction == Direction.dLeftToRight ? e.nextInAEL: e.prevInAEL; + } + //------------------------------------------------------------------------------ + + private bool IsMinima(TEdge e) + { + return e != null && (e.prev.nextInLML != e) && (e.next.nextInLML != e); + } + //------------------------------------------------------------------------------ + + private bool IsMaxima(TEdge e, double Y) + { + return (e != null && e.ytop == Y && e.nextInLML == null); + } + //------------------------------------------------------------------------------ + + private bool IsIntermediate(TEdge e, double Y) + { + return (e.ytop == Y && e.nextInLML != null); + } + //------------------------------------------------------------------------------ + + private TEdge GetMaximaPair(TEdge e) + { + if (!IsMaxima(e.next, e.ytop) || (e.next.xtop != e.xtop)) + return e.prev; else + return e.next; + } + //------------------------------------------------------------------------------ + + private bool ProcessIntersections(Int64 botY, Int64 topY) + { + if( m_ActiveEdges == null ) return true; + try { + BuildIntersectList(botY, topY); + if ( m_IntersectNodes == null) return true; + if ( FixupIntersections() ) ProcessIntersectList(); + else return false; + } + catch { + m_SortedEdges = null; + DisposeIntersectNodes(); + throw new ClipperException("ProcessIntersections error"); + } + return true; + } + //------------------------------------------------------------------------------ + + private void BuildIntersectList(Int64 botY, Int64 topY) + { + if ( m_ActiveEdges == null ) return; + + //prepare for sorting ... + TEdge e = m_ActiveEdges; + m_SortedEdges = e; + while( e != null ) + { + e.prevInSEL = e.prevInAEL; + e.nextInSEL = e.nextInAEL; + e.tmpX = TopX( e, topY ); + e = e.nextInAEL; + } + + //bubblesort ... + bool isModified = true; + while( isModified && m_SortedEdges != null ) + { + isModified = false; + e = m_SortedEdges; + while( e.nextInSEL != null ) + { + TEdge eNext = e.nextInSEL; + IntPoint pt = new IntPoint(); + if(e.tmpX > eNext.tmpX && IntersectPoint(e, eNext, ref pt)) + { + if (pt.Y > botY) + { + pt.Y = botY; + pt.X = TopX(e, pt.Y); + } + AddIntersectNode(e, eNext, pt); + SwapPositionsInSEL(e, eNext); + isModified = true; + } + else + e = eNext; + } + if( e.prevInSEL != null ) e.prevInSEL.nextInSEL = null; + else break; + } + m_SortedEdges = null; + } + //------------------------------------------------------------------------------ + + private bool FixupIntersections() + { + if ( m_IntersectNodes.next == null ) return true; + + CopyAELToSEL(); + IntersectNode int1 = m_IntersectNodes; + IntersectNode int2 = m_IntersectNodes.next; + while (int2 != null) + { + TEdge e1 = int1.edge1; + TEdge e2; + if (e1.prevInSEL == int1.edge2) e2 = e1.prevInSEL; + else if (e1.nextInSEL == int1.edge2) e2 = e1.nextInSEL; + else + { + //The current intersection is out of order, so try and swap it with + //a subsequent intersection ... + while (int2 != null) + { + if (int2.edge1.nextInSEL == int2.edge2 || + int2.edge1.prevInSEL == int2.edge2) break; + else int2 = int2.next; + } + if (int2 == null) return false; //oops!!! + + //found an intersect node that can be swapped ... + SwapIntersectNodes(int1, int2); + e1 = int1.edge1; + e2 = int1.edge2; + } + SwapPositionsInSEL(e1, e2); + int1 = int1.next; + int2 = int1.next; + } + + m_SortedEdges = null; + + //finally, check the last intersection too ... + return (int1.edge1.prevInSEL == int1.edge2 || int1.edge1.nextInSEL == int1.edge2); + } + //------------------------------------------------------------------------------ + + private void ProcessIntersectList() + { + while( m_IntersectNodes != null ) + { + IntersectNode iNode = m_IntersectNodes.next; + { + IntersectEdges( m_IntersectNodes.edge1 , + m_IntersectNodes.edge2 , m_IntersectNodes.pt, Protects.ipBoth ); + SwapPositionsInAEL( m_IntersectNodes.edge1 , m_IntersectNodes.edge2 ); + } + m_IntersectNodes = null; + m_IntersectNodes = iNode; + } + } + //------------------------------------------------------------------------------ + + private static Int64 Round(double value) + { + return value < 0 ? (Int64)(value - 0.5) : (Int64)(value + 0.5); + } + //------------------------------------------------------------------------------ + + private static Int64 TopX(TEdge edge, Int64 currentY) + { + if (currentY == edge.ytop) + return edge.xtop; + return edge.xbot + Round(edge.dx *(currentY - edge.ybot)); + } + //------------------------------------------------------------------------------ + + private void AddIntersectNode(TEdge e1, TEdge e2, IntPoint pt) + { + IntersectNode newNode = new IntersectNode(); + newNode.edge1 = e1; + newNode.edge2 = e2; + newNode.pt = pt; + newNode.next = null; + if (m_IntersectNodes == null) m_IntersectNodes = newNode; + else if (ProcessParam1BeforeParam2(newNode, m_IntersectNodes)) + { + newNode.next = m_IntersectNodes; + m_IntersectNodes = newNode; + } + else + { + IntersectNode iNode = m_IntersectNodes; + while (iNode.next != null && ProcessParam1BeforeParam2(iNode.next, newNode)) + iNode = iNode.next; + newNode.next = iNode.next; + iNode.next = newNode; + } + } + //------------------------------------------------------------------------------ + + private bool ProcessParam1BeforeParam2(IntersectNode node1, IntersectNode node2) + { + bool result; + if (node1.pt.Y == node2.pt.Y) + { + if (node1.edge1 == node2.edge1 || node1.edge2 == node2.edge1) + { + result = node2.pt.X > node1.pt.X; + return node2.edge1.dx > 0 ? !result : result; + } + else if (node1.edge1 == node2.edge2 || node1.edge2 == node2.edge2) + { + result = node2.pt.X > node1.pt.X; + return node2.edge2.dx > 0 ? !result : result; + } + else return node2.pt.X > node1.pt.X; + } + else return node1.pt.Y > node2.pt.Y; + } + //------------------------------------------------------------------------------ + + private void SwapIntersectNodes(IntersectNode int1, IntersectNode int2) + { + TEdge e1 = int1.edge1; + TEdge e2 = int1.edge2; + IntPoint p = int1.pt; + int1.edge1 = int2.edge1; + int1.edge2 = int2.edge2; + int1.pt = int2.pt; + int2.edge1 = e1; + int2.edge2 = e2; + int2.pt = p; + } + //------------------------------------------------------------------------------ + + private bool IntersectPoint(TEdge edge1, TEdge edge2, ref IntPoint ip) + { + double b1, b2; + if (SlopesEqual(edge1, edge2, m_UseFullRange)) return false; + else if (edge1.dx == 0) + { + ip.X = edge1.xbot; + if (edge2.dx == horizontal) + { + ip.Y = edge2.ybot; + } else + { + b2 = edge2.ybot - (edge2.xbot / edge2.dx); + ip.Y = Round(ip.X / edge2.dx + b2); + } + } + else if (edge2.dx == 0) + { + ip.X = edge2.xbot; + if (edge1.dx == horizontal) + { + ip.Y = edge1.ybot; + } else + { + b1 = edge1.ybot - (edge1.xbot / edge1.dx); + ip.Y = Round(ip.X / edge1.dx + b1); + } + } else + { + b1 = edge1.xbot - edge1.ybot * edge1.dx; + b2 = edge2.xbot - edge2.ybot * edge2.dx; + double q = (b2-b1) / (edge1.dx - edge2.dx); + ip.Y = Round(q); + if (Math.Abs(edge1.dx) < Math.Abs(edge2.dx)) + ip.X = Round(edge1.dx * q + b1); + else + ip.X = Round(edge2.dx * q + b2); + } + + if (ip.Y < edge1.ytop || ip.Y < edge2.ytop) + { + if (edge1.ytop > edge2.ytop) + { + ip.X = edge1.xtop; + ip.Y = edge1.ytop; + return TopX(edge2, edge1.ytop) < edge1.xtop; + } + else + { + ip.X = edge2.xtop; + ip.Y = edge2.ytop; + return TopX(edge1, edge2.ytop) > edge2.xtop; + } + } + else + return true; + } + //------------------------------------------------------------------------------ + + private void DisposeIntersectNodes() + { + while ( m_IntersectNodes != null ) + { + IntersectNode iNode = m_IntersectNodes.next; + m_IntersectNodes = null; + m_IntersectNodes = iNode; + } + } + //------------------------------------------------------------------------------ + + private void ProcessEdgesAtTopOfScanbeam(Int64 topY) + { + TEdge e = m_ActiveEdges; + while( e != null ) + { + //1. process maxima, treating them as if they're 'bent' horizontal edges, + // but exclude maxima with horizontal edges. nb: e can't be a horizontal. + if( IsMaxima(e, topY) && GetMaximaPair(e).dx != horizontal ) + { + //'e' might be removed from AEL, as may any following edges so ... + TEdge ePrev = e.prevInAEL; + DoMaxima(e, topY); + if( ePrev == null ) e = m_ActiveEdges; + else e = ePrev.nextInAEL; + } + else + { + //2. promote horizontal edges, otherwise update xcurr and ycurr ... + if( IsIntermediate(e, topY) && e.nextInLML.dx == horizontal ) + { + if (e.outIdx >= 0) + { + AddOutPt(e, new IntPoint(e.xtop, e.ytop)); + + for (int i = 0; i < m_HorizJoins.Count; ++i) + { + IntPoint pt = new IntPoint(), pt2 = new IntPoint(); + HorzJoinRec hj = m_HorizJoins[i]; + if (GetOverlapSegment(new IntPoint(hj.edge.xbot, hj.edge.ybot), + new IntPoint(hj.edge.xtop, hj.edge.ytop), + new IntPoint(e.nextInLML.xbot, e.nextInLML.ybot), + new IntPoint(e.nextInLML.xtop, e.nextInLML.ytop), ref pt, ref pt2)) + AddJoin(hj.edge, e.nextInLML, hj.savedIdx, e.outIdx); + } + + AddHorzJoin(e.nextInLML, e.outIdx); + } + UpdateEdgeIntoAEL(ref e); + AddEdgeToSEL(e); + } + else + { + //this just simplifies horizontal processing ... + e.xcurr = TopX( e, topY ); + e.ycurr = topY; + } + e = e.nextInAEL; + } + } + + //3. Process horizontals at the top of the scanbeam ... + ProcessHorizontals(); + + //4. Promote intermediate vertices ... + e = m_ActiveEdges; + while( e != null ) + { + if( IsIntermediate( e, topY ) ) + { + if (e.outIdx >= 0) AddOutPt(e, new IntPoint(e.xtop, e.ytop)); + UpdateEdgeIntoAEL(ref e); + + //if output polygons share an edge, they'll need joining later ... + TEdge ePrev = e.prevInAEL; + TEdge eNext = e.nextInAEL; + if (ePrev != null && ePrev.xcurr == e.xbot && + ePrev.ycurr == e.ybot && e.outIdx >= 0 && + ePrev.outIdx >= 0 && ePrev.ycurr > ePrev.ytop && + SlopesEqual(e, ePrev, m_UseFullRange)) + { + AddOutPt(ePrev, new IntPoint(e.xbot, e.ybot)); + AddJoin(e, ePrev, -1, -1); + } + else if (eNext != null && eNext.xcurr == e.xbot && + eNext.ycurr == e.ybot && e.outIdx >= 0 && + eNext.outIdx >= 0 && eNext.ycurr > eNext.ytop && + SlopesEqual(e, eNext, m_UseFullRange)) + { + AddOutPt(eNext, new IntPoint(e.xbot, e.ybot)); + AddJoin(e, eNext, -1, -1); + } + } + e = e.nextInAEL; + } + } + //------------------------------------------------------------------------------ + + private void DoMaxima(TEdge e, Int64 topY) + { + TEdge eMaxPair = GetMaximaPair(e); + Int64 X = e.xtop; + TEdge eNext = e.nextInAEL; + while( eNext != eMaxPair ) + { + if (eNext == null) throw new ClipperException("DoMaxima error"); + IntersectEdges( e, eNext, new IntPoint(X, topY), Protects.ipBoth ); + SwapPositionsInAEL(e, eNext); + eNext = eNext.nextInAEL; + } + if( e.outIdx < 0 && eMaxPair.outIdx < 0 ) + { + DeleteFromAEL( e ); + DeleteFromAEL( eMaxPair ); + } + else if( e.outIdx >= 0 && eMaxPair.outIdx >= 0 ) + { + IntersectEdges(e, eMaxPair, new IntPoint(X, topY), Protects.ipNone); + } + else throw new ClipperException("DoMaxima error"); + } + //------------------------------------------------------------------------------ + + public static void ReversePolygons(Polygons polys) + { + polys.ForEach(delegate(Polygon poly) { poly.Reverse(); }); + } + //------------------------------------------------------------------------------ + + public static bool Orientation(Polygon poly) + { + return Area(poly) >= 0; + } + //------------------------------------------------------------------------------ + + private int PointCount(OutPt pts) + { + if (pts == null) return 0; + int result = 0; + OutPt p = pts; + do + { + result++; + p = p.next; + } + while (p != pts); + return result; + } + //------------------------------------------------------------------------------ + + private void BuildResult(Polygons polyg) + { + polyg.Clear(); + polyg.Capacity = m_PolyOuts.Count; + for (int i = 0; i < m_PolyOuts.Count; i++) + { + OutRec outRec = m_PolyOuts[i]; + if (outRec.pts == null) continue; + OutPt p = outRec.pts; + int cnt = PointCount(p); + if (cnt < 3) continue; + Polygon pg = new Polygon(cnt); + for (int j = 0; j < cnt; j++) + { + pg.Add(p.pt); + p = p.prev; + } + polyg.Add(pg); + } + } + //------------------------------------------------------------------------------ + + private void BuildResult2(PolyTree polytree) + { + polytree.Clear(); + + //add each output polygon/contour to polytree ... + polytree.m_AllPolys.Capacity = m_PolyOuts.Count; + for (int i = 0; i < m_PolyOuts.Count; i++) + { + OutRec outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec.pts); + if (cnt < 3) continue; + FixHoleLinkage(outRec); + PolyNode pn = new PolyNode(); + polytree.m_AllPolys.Add(pn); + outRec.polyNode = pn; + pn.m_polygon.Capacity = cnt; + OutPt op = outRec.pts; + for (int j = 0; j < cnt; j++) + { + pn.m_polygon.Add(op.pt); + op = op.prev; + } + } + + //fixup PolyNode links etc ... + polytree.m_Childs.Capacity = m_PolyOuts.Count; + for (int i = 0; i < m_PolyOuts.Count; i++) + { + OutRec outRec = m_PolyOuts[i]; + if (outRec.polyNode == null) continue; + if (outRec.FirstLeft == null) + polytree.AddChild(outRec.polyNode); + else + outRec.FirstLeft.polyNode.AddChild(outRec.polyNode); + } + } + //------------------------------------------------------------------------------ + + private void FixupOutPolygon(OutRec outRec) + { + //FixupOutPolygon() - removes duplicate points and simplifies consecutive + //parallel edges by removing the middle vertex. + OutPt lastOK = null; + outRec.pts = outRec.bottomPt; + OutPt pp = outRec.bottomPt; + for (;;) + { + if (pp.prev == pp || pp.prev == pp.next) + { + DisposeOutPts(pp); + outRec.pts = null; + outRec.bottomPt = null; + return; + } + //test for duplicate points and for same slope (cross-product) ... + if (PointsEqual(pp.pt, pp.next.pt) || + SlopesEqual(pp.prev.pt, pp.pt, pp.next.pt, m_UseFullRange)) + { + lastOK = null; + OutPt tmp = pp; + if (pp == outRec.bottomPt) + outRec.bottomPt = null; //flags need for updating + pp.prev.next = pp.next; + pp.next.prev = pp.prev; + pp = pp.prev; + tmp = null; + } + else if (pp == lastOK) break; + else + { + if (lastOK == null) lastOK = pp; + pp = pp.next; + } + } + if (outRec.bottomPt == null) + { + outRec.bottomPt = GetBottomPt(pp); + outRec.bottomPt.idx = outRec.idx; + outRec.pts = outRec.bottomPt; + } + } + //------------------------------------------------------------------------------ + + private bool JoinPoints(JoinRec j, out OutPt p1, out OutPt p2) + { + p1 = null; p2 = null; + OutRec outRec1 = m_PolyOuts[j.poly1Idx]; + OutRec outRec2 = m_PolyOuts[j.poly2Idx]; + if (outRec1 == null || outRec2 == null) return false; + OutPt pp1a = outRec1.pts; + OutPt pp2a = outRec2.pts; + IntPoint pt1 = j.pt2a, pt2 = j.pt2b; + IntPoint pt3 = j.pt1a, pt4 = j.pt1b; + if (!FindSegment(ref pp1a, ref pt1, ref pt2)) return false; + if (outRec1 == outRec2) + { + //we're searching the same polygon for overlapping segments so + //segment 2 mustn't be the same as segment 1 ... + pp2a = pp1a.next; + if (!FindSegment(ref pp2a, ref pt3, ref pt4) || (pp2a == pp1a)) return false; + } + else if (!FindSegment(ref pp2a, ref pt3, ref pt4)) return false; + + if (!GetOverlapSegment(pt1, pt2, pt3, pt4, ref pt1, ref pt2)) return false; + + OutPt p3, p4, prev = pp1a.prev; + //get p1 & p2 polypts - the overlap start & endpoints on poly1 + if (PointsEqual(pp1a.pt, pt1)) p1 = pp1a; + else if (PointsEqual(prev.pt, pt1)) p1 = prev; + else p1 = InsertPolyPtBetween(pp1a, prev, pt1); + + if (PointsEqual(pp1a.pt, pt2)) p2 = pp1a; + else if (PointsEqual(prev.pt, pt2)) p2 = prev; + else if ((p1 == pp1a) || (p1 == prev)) + p2 = InsertPolyPtBetween(pp1a, prev, pt2); + else if (Pt3IsBetweenPt1AndPt2(pp1a.pt, p1.pt, pt2)) + p2 = InsertPolyPtBetween(pp1a, p1, pt2); else + p2 = InsertPolyPtBetween(p1, prev, pt2); + + //get p3 & p4 polypts - the overlap start & endpoints on poly2 + prev = pp2a.prev; + if (PointsEqual(pp2a.pt, pt1)) p3 = pp2a; + else if (PointsEqual(prev.pt, pt1)) p3 = prev; + else p3 = InsertPolyPtBetween(pp2a, prev, pt1); + + if (PointsEqual(pp2a.pt, pt2)) p4 = pp2a; + else if (PointsEqual(prev.pt, pt2)) p4 = prev; + else if ((p3 == pp2a) || (p3 == prev)) + p4 = InsertPolyPtBetween(pp2a, prev, pt2); + else if (Pt3IsBetweenPt1AndPt2(pp2a.pt, p3.pt, pt2)) + p4 = InsertPolyPtBetween(pp2a, p3, pt2); else + p4 = InsertPolyPtBetween(p3, prev, pt2); + + //p1.pt == p3.pt and p2.pt == p4.pt so join p1 to p3 and p2 to p4 ... + if (p1.next == p2 && p3.prev == p4) + { + p1.next = p3; + p3.prev = p1; + p2.prev = p4; + p4.next = p2; + return true; + } + else if (p1.prev == p2 && p3.next == p4) + { + p1.prev = p3; + p3.next = p1; + p2.next = p4; + p4.prev = p2; + return true; + } + else + return false; //an orientation is probably wrong + } + //---------------------------------------------------------------------- + + private void FixupJoinRecs(JoinRec j, OutPt pt, int startIdx) + { + for (int k = startIdx; k < m_Joins.Count; k++) + { + JoinRec j2 = m_Joins[k]; + if (j2.poly1Idx == j.poly1Idx && PointIsVertex(j2.pt1a, pt)) + j2.poly1Idx = j.poly2Idx; + if (j2.poly2Idx == j.poly1Idx && PointIsVertex(j2.pt2a, pt)) + j2.poly2Idx = j.poly2Idx; + } + } + //---------------------------------------------------------------------- + + private bool Poly2ContainsPoly1(OutPt outPt1, OutPt outPt2, bool UseFullInt64Range) + { + //find the first pt in outPt1 that isn't also a vertex of outPt2 ... + OutPt outPt = outPt1; + do + { + if (!PointIsVertex(outPt.pt, outPt2)) break; + outPt = outPt.next; + } + while (outPt != outPt1); + bool result; + //sometimes a point on one polygon can be touching the other polygon + //so to be totally confident outPt1 is inside outPt2 repeat ... + do + { + result = PointInPolygon(outPt.pt, outPt2, UseFullInt64Range); + outPt = outPt.next; + } + while (result && outPt != outPt1); + return result; + } + //---------------------------------------------------------------------- + + private void FixupFirstLefts1(OutRec OldOutRec, OutRec NewOutRec) + { + for (int i = 0; i < m_PolyOuts.Count; i++) + { + OutRec outRec = m_PolyOuts[i]; + if (outRec.pts != null && outRec.FirstLeft == OldOutRec) + { + if (Poly2ContainsPoly1(outRec.pts, NewOutRec.pts, m_UseFullRange)) + outRec.FirstLeft = NewOutRec; + } + } + } + //---------------------------------------------------------------------- + + private void FixupFirstLefts2(OutRec OldOutRec, OutRec NewOutRec) + { + foreach (OutRec outRec in m_PolyOuts) + if (outRec.FirstLeft == OldOutRec) outRec.FirstLeft = NewOutRec; + } + //---------------------------------------------------------------------- + + private void JoinCommonEdges() + { + for (int i = 0; i < m_Joins.Count; i++) + { + JoinRec j = m_Joins[i]; + + OutRec outRec1 = m_PolyOuts[j.poly1Idx]; + OutRec outRec2 = m_PolyOuts[j.poly2Idx]; + + if (outRec1.pts == null || outRec2.pts == null) continue; + + //get the polygon fragment with the correct hole state (FirstLeft) + //before calling JoinPoints() ... + OutRec holeStateRec; + if (outRec1 == outRec2) holeStateRec = outRec1; + else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; + else holeStateRec = GetLowermostRec(outRec1, outRec2); + + OutPt p1, p2; + if (!JoinPoints(j, out p1, out p2)) continue; + + if (outRec1 == outRec2) + { + //instead of joining two polygons, we've just created a new one by + //splitting one polygon into two. + outRec1.pts = GetBottomPt(p1); + outRec1.bottomPt = outRec1.pts; + outRec1.bottomPt.idx = outRec1.idx; + outRec2 = CreateOutRec(); + m_PolyOuts.Add(outRec2); + outRec2.idx = m_PolyOuts.Count - 1; + j.poly2Idx = outRec2.idx; + outRec2.pts = GetBottomPt(p2); + outRec2.bottomPt = outRec2.pts; + outRec2.bottomPt.idx = outRec2.idx; + + if (Poly2ContainsPoly1(outRec2.pts, outRec1.pts, m_UseFullRange)) + { + //outRec2 is contained by outRec1 ... + outRec2.isHole = !outRec1.isHole; + outRec2.FirstLeft = outRec1; + + FixupJoinRecs(j, p2, i + 1); + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + + FixupOutPolygon(outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(outRec2); // but AFTER calling FixupJoinRecs() + + if ((outRec2.isHole ^ m_ReverseOutput) == (Area(outRec2, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec2.pts); + + } + else if (Poly2ContainsPoly1(outRec1.pts, outRec2.pts, m_UseFullRange)) + { + //outRec1 is contained by outRec2 ... + outRec2.isHole = outRec1.isHole; + outRec1.isHole = !outRec2.isHole; + outRec2.FirstLeft = outRec1.FirstLeft; + outRec1.FirstLeft = outRec2; + + FixupJoinRecs(j, p2, i + 1); + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); + + FixupOutPolygon(outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(outRec2); // but AFTER calling FixupJoinRecs() + + if ((outRec1.isHole ^ m_ReverseOutput) == (Area(outRec1, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec1.pts); + } + else + { + //the 2 polygons are completely separate ... + outRec2.isHole = outRec1.isHole; + outRec2.FirstLeft = outRec1.FirstLeft; + + FixupJoinRecs(j, p2, i + 1); + + //fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); + + FixupOutPolygon(outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(outRec2); // but AFTER calling FixupJoinRecs() + } + } + else + { + //joined 2 polygons together ... + + //cleanup redundant edges ... + FixupOutPolygon(outRec1); + + //delete the obsolete pointer ... + int OKIdx = outRec1.idx; + int ObsoleteIdx = outRec2.idx; + outRec2.pts = null; + outRec2.bottomPt = null; + + outRec1.isHole = holeStateRec.isHole; + if (holeStateRec == outRec2) + outRec1.FirstLeft = outRec2.FirstLeft; + outRec2.FirstLeft = outRec1; + + //now fixup any subsequent joins that match this polygon + for (int k = i + 1; k < m_Joins.Count; k++) + { + JoinRec j2 = m_Joins[k]; + if (j2.poly1Idx == ObsoleteIdx) j2.poly1Idx = OKIdx; + if (j2.poly2Idx == ObsoleteIdx) j2.poly2Idx = OKIdx; + } + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + } + } + } + //------------------------------------------------------------------------------ + + private static bool FullRangeNeeded(Polygon pts) + { + bool result = false; + for (int i = 0; i < pts.Count; i++) + { + if (Math.Abs(pts[i].X) > hiRange || Math.Abs(pts[i].Y) > hiRange) + throw new ClipperException("Coordinate exceeds range bounds."); + else if (Math.Abs(pts[i].X) > loRange || Math.Abs(pts[i].Y) > loRange) + result = true; + } + return result; + } + //------------------------------------------------------------------------------ + + public static double Area(Polygon poly) + { + int highI = poly.Count - 1; + if (highI < 2) return 0; + if (FullRangeNeeded(poly)) + { + Int128 a = new Int128(0); + a = Int128.Int128Mul(poly[highI].X + poly[0].X, poly[0].Y - poly[highI].Y); + for (int i = 1; i <= highI; ++i) + a += Int128.Int128Mul(poly[i - 1].X + poly[i].X, poly[i].Y - poly[i - 1].Y); + return a.ToDouble() / 2; + } + else + { + double area = ((double)poly[highI].X + poly[0].X) * ((double)poly[0].Y - poly[highI].Y); + for (int i = 1; i <= highI; ++i) + area += ((double)poly[i - 1].X + poly[i].X) * ((double)poly[i].Y - poly[i -1].Y); + return area / 2; + } + } + //------------------------------------------------------------------------------ + + double Area(OutRec outRec, bool UseFull64BitRange) + { + OutPt op = outRec.pts; + if (op == null) return 0; + if (UseFull64BitRange) + { + Int128 a = new Int128(0); + do + { + a += Int128.Int128Mul(op.pt.X + op.prev.pt.X, op.prev.pt.Y - op.pt.Y); + op = op.next; + } while (op != outRec.pts); + return a.ToDouble() / 2; + } + else + { + double a = 0; + do { + a = a + (op.pt.X + op.prev.pt.X) * (op.prev.pt.Y - op.pt.Y); + op = op.next; + } while (op != outRec.pts); + return a/2; + } + } + + //------------------------------------------------------------------------------ + // OffsetPolygon functions ... + //------------------------------------------------------------------------------ + + internal static Polygon BuildArc(IntPoint pt, double a1, double a2, double r) + { + Int64 steps = Math.Max(6, (int)(Math.Sqrt(Math.Abs(r)) * Math.Abs(a2 - a1))); + if (steps > 0x100) steps = 0x100; + int n = (int)steps; + Polygon result = new Polygon(n); + double da = (a2 - a1) / (n - 1); + double a = a1; + for (int i = 0; i < n; ++i) + { + result.Add(new IntPoint(pt.X + Round(Math.Cos(a) * r), pt.Y + Round(Math.Sin(a) * r))); + a += da; + } + return result; + } + //------------------------------------------------------------------------------ + + internal static DoublePoint GetUnitNormal(IntPoint pt1, IntPoint pt2) + { + double dx = (pt2.X - pt1.X); + double dy = (pt2.Y - pt1.Y); + if ((dx == 0) && (dy == 0)) return new DoublePoint(); + + double f = 1 * 1.0 / Math.Sqrt(dx * dx + dy * dy); + dx *= f; + dy *= f; + + return new DoublePoint(dy, -dx); + } + //------------------------------------------------------------------------------ + + internal class DoublePoint + { + public double X { get; set; } + public double Y { get; set; } + public DoublePoint(double x = 0, double y = 0) + { + this.X = x; this.Y = y; + } + }; + //------------------------------------------------------------------------------ + + private class PolyOffsetBuilder + { + private Polygons pts; + private Polygon currentPoly; + private List normals; + private double delta, m_R; + private int m_i, m_j, m_k; + private const int buffLength = 128; + + public PolyOffsetBuilder(Polygons pts, Polygons solution, double delta, + JoinType jointype, double MiterLimit = 2, bool AutoFix = true) + { + //precondtion: solution != pts + + if (delta == 0) + { + solution = pts; + return; + } + + this.pts = pts; + this.delta = delta; + + //AutoFix - fixes polygon orientation if necessary and removes + //duplicate vertices. Can be set false when you're sure that polygon + //orientation is correct and that there are no duplicate vertices. + if (AutoFix) + { + int Len = pts.Count, botI = 0; + while (botI < Len && pts[botI].Count == 0) botI++; + if (botI == Len) return; + + //botPt: used to find the lowermost (in inverted Y-axis) & leftmost point + //This point (on pts[botI]) must be on an outer polygon ring and if + //its orientation is false (counterclockwise) then assume all polygons + //need reversing ... + IntPoint botPt = pts[botI][0]; + for (int i = botI; i < Len; ++i) + { + if (pts[i].Count == 0) continue; + if (UpdateBotPt(pts[i][0], ref botPt)) botI = i; + for (int j = pts[i].Count -1; j > 0; j--) + { + if (PointsEqual(pts[i][j], pts[i][j -1])) + pts[i].RemoveAt(j); + else if (UpdateBotPt(pts[i][j], ref botPt)) + botI = i; + } + } + if (!Orientation(pts[botI])) + ReversePolygons(pts); + } + + if (MiterLimit <= 1) MiterLimit = 1; + double RMin = 2.0 / (MiterLimit*MiterLimit); + + normals = new List(); + + double deltaSq = delta*delta; + solution.Clear(); + solution.Capacity = pts.Count; + for (m_i = 0; m_i < pts.Count; m_i++) + { + int len = pts[m_i].Count; + if (len > 1 && pts[m_i][0].X == pts[m_i][len - 1].X && + pts[m_i][0].Y == pts[m_i][len - 1].Y) len--; + + if (len == 0 || (len < 3 && delta <= 0)) + continue; + else if (len == 1) + { + Polygon arc; + arc = BuildArc(pts[m_i][len - 1], 0, 2 * Math.PI, delta); + solution.Add(arc); + continue; + } + + //build normals ... + normals.Clear(); + normals.Capacity = len; + for (int j = 0; j < len -1; ++j) + normals.Add(GetUnitNormal(pts[m_i][j], pts[m_i][j+1])); + normals.Add(GetUnitNormal(pts[m_i][len - 1], pts[m_i][0])); + + currentPoly = new Polygon(); + m_k = len - 1; + for (m_j = 0; m_j < len; ++m_j) + { + switch (jointype) + { + case JoinType.jtMiter: + { + m_R = 1 + (normals[m_j].X*normals[m_k].X + + normals[m_j].Y*normals[m_k].Y); + if (m_R >= RMin) DoMiter(); else DoSquare(MiterLimit); + break; + } + case JoinType.jtRound: + DoRound(); + break; + case JoinType.jtSquare: + DoSquare(1); + break; + } + m_k = m_j; + } + solution.Add(currentPoly); + } + + //finally, clean up untidy corners ... + Clipper clpr = new Clipper(); + clpr.AddPolygons(solution, PolyType.ptSubject); + if (delta > 0) + { + clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftPositive, PolyFillType.pftPositive); + } + else + { + IntRect r = clpr.GetBounds(); + Polygon outer = new Polygon(4); + + outer.Add(new IntPoint(r.left - 10, r.bottom + 10)); + outer.Add(new IntPoint(r.right + 10, r.bottom + 10)); + outer.Add(new IntPoint(r.right + 10, r.top - 10)); + outer.Add(new IntPoint(r.left - 10, r.top - 10)); + + clpr.AddPolygon(outer, PolyType.ptSubject); + clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftNegative, PolyFillType.pftNegative); + if (solution.Count > 0) + { + solution.RemoveAt(0); + for (int i = 0; i < solution.Count; i++) + solution[i].Reverse(); + } + } + } + //------------------------------------------------------------------------------ + + internal bool UpdateBotPt(IntPoint pt, ref IntPoint botPt) + { + if (pt.Y > botPt.Y || (pt.Y == botPt.Y && pt.X < botPt.X)) + { + botPt = pt; + return true; + } + else return false; + } + //------------------------------------------------------------------------------ + + internal void AddPoint(IntPoint pt) + { + int len = currentPoly.Count; + if (len == currentPoly.Capacity) + currentPoly.Capacity = len + buffLength; + currentPoly.Add(pt); + } + //------------------------------------------------------------------------------ + + internal void DoSquare(double mul) + { + IntPoint pt1 = new IntPoint((Int64)Round(pts[m_i][m_j].X + normals[m_k].X * delta), + (Int64)Round(pts[m_i][m_j].Y + normals[m_k].Y * delta)); + IntPoint pt2 = new IntPoint((Int64)Round(pts[m_i][m_j].X + normals[m_j].X * delta), + (Int64)Round(pts[m_i][m_j].Y + normals[m_j].Y * delta)); + if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * delta >= 0) + { + double a1 = Math.Atan2(normals[m_k].Y, normals[m_k].X); + double a2 = Math.Atan2(-normals[m_j].Y, -normals[m_j].X); + a1 = Math.Abs(a2 - a1); + if (a1 > Math.PI) a1 = Math.PI * 2 - a1; + double dx = Math.Tan((Math.PI - a1) / 4) * Math.Abs(delta * mul); + pt1 = new IntPoint((Int64)(pt1.X - normals[m_k].Y * dx), + (Int64)(pt1.Y + normals[m_k].X * dx)); + AddPoint(pt1); + pt2 = new IntPoint((Int64)(pt2.X + normals[m_j].Y * dx), + (Int64)(pt2.Y - normals[m_j].X * dx)); + AddPoint(pt2); + } + else + { + AddPoint(pt1); + AddPoint(pts[m_i][m_j]); + AddPoint(pt2); + } + } + //------------------------------------------------------------------------------ + + internal void DoMiter() + { + if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * delta >= 0) + { + double q = delta / m_R; + AddPoint(new IntPoint((Int64)Round(pts[m_i][m_j].X + + (normals[m_k].X + normals[m_j].X) * q), + (Int64)Round(pts[m_i][m_j].Y + (normals[m_k].Y + normals[m_j].Y) * q))); + } + else + { + IntPoint pt1 = new IntPoint((Int64)Round(pts[m_i][m_j].X + normals[m_k].X * delta), + (Int64)Round(pts[m_i][m_j].Y + normals[m_k].Y * delta)); + IntPoint pt2 = new IntPoint((Int64)Round(pts[m_i][m_j].X + normals[m_j].X * delta), + (Int64)Round(pts[m_i][m_j].Y + normals[m_j].Y * delta)); + AddPoint(pt1); + AddPoint(pts[m_i][m_j]); + AddPoint(pt2); + } + } + //------------------------------------------------------------------------------ + + internal void DoRound() + { + IntPoint pt1 = new IntPoint(Round(pts[m_i][m_j].X + normals[m_k].X * delta), + Round(pts[m_i][m_j].Y + normals[m_k].Y * delta)); + IntPoint pt2 = new IntPoint(Round(pts[m_i][m_j].X + normals[m_j].X * delta), + Round(pts[m_i][m_j].Y + normals[m_j].Y * delta)); + AddPoint(pt1); + //round off reflex angles (ie > 180 deg) unless almost flat (ie < 10deg). + //cross product normals < 0 . angle > 180 deg. + //dot product normals == 1 . no angle + if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * delta >= 0) + { + if ((normals[m_j].X * normals[m_k].X + normals[m_j].Y * normals[m_k].Y) < 0.985) + { + double a1 = Math.Atan2(normals[m_k].Y, normals[m_k].X); + double a2 = Math.Atan2(normals[m_j].Y, normals[m_j].X); + if (delta > 0 && a2 < a1) a2 += Math.PI * 2; + else if (delta < 0 && a2 > a1) a2 -= Math.PI * 2; + Polygon arc = BuildArc(pts[m_i][m_j], a1, a2, delta); + for (int m = 0; m < arc.Count; m++) + AddPoint(arc[m]); + } + } + else + AddPoint(pts[m_i][m_j]); + AddPoint(pt2); + } + //------------------------------------------------------------------------------ + + } //end PolyOffsetBuilder + //------------------------------------------------------------------------------ + + public static Polygons OffsetPolygons(Polygons poly, double delta, + JoinType jointype, double MiterLimit, bool AutoFix) + { + Polygons result = new Polygons(poly.Count); + new PolyOffsetBuilder(poly, result, delta, jointype, MiterLimit, AutoFix); + return result; + } + //------------------------------------------------------------------------------ + + public static Polygons OffsetPolygons(Polygons poly, double delta, + JoinType jointype, double MiterLimit) + { + Polygons result = new Polygons(poly.Count); + new PolyOffsetBuilder(poly, result, delta, jointype, MiterLimit, true); + return result; + } + //------------------------------------------------------------------------------ + + public static Polygons OffsetPolygons(Polygons poly, double delta, JoinType jointype) + { + Polygons result = new Polygons(poly.Count); + new PolyOffsetBuilder(poly, result, delta, jointype, 2.0, true); + return result; + } + //------------------------------------------------------------------------------ + + public static Polygons OffsetPolygons(Polygons poly, double delta) + { + Polygons result = new Polygons(poly.Count); + new PolyOffsetBuilder(poly, result, delta, JoinType.jtSquare, 2.0, true); + return result; + } + + //------------------------------------------------------------------------------ + // SimplifyPolygon functions ... + // Convert self-intersecting polygons into simple polygons + //------------------------------------------------------------------------------ + + public static Polygons SimplifyPolygon(Polygon poly, + PolyFillType fillType = PolyFillType.pftEvenOdd) + { + Polygons result = new Polygons(); + Clipper c = new Clipper(); + c.AddPolygon(poly, PolyType.ptSubject); + c.Execute(ClipType.ctUnion, result, fillType, fillType); + return result; + } + //------------------------------------------------------------------------------ + + public static Polygons SimplifyPolygons(Polygons polys, + PolyFillType fillType = PolyFillType.pftEvenOdd) + { + Polygons result = new Polygons(); + Clipper c = new Clipper(); + c.AddPolygons(polys, PolyType.ptSubject); + c.Execute(ClipType.ctUnion, result, fillType, fillType); + return result; + } + //------------------------------------------------------------------------------ + + public static Polygon CleanPolygon(Polygon poly, + double delta = 1.415) + { + //delta = proximity in units/pixels below which vertices + //will be stripped. Default ~= sqrt(2) so when adjacent + //vertices have both x & y coords within 1 unit, then + //the second vertex will be stripped. + int len = poly.Count; + if (len < 3) return null; + Polygon result = new Polygon(poly); + int d = (int)(delta * delta); + IntPoint p = poly[0]; + int j = 1; + for (int i = 1; i < len; i++) + { + if ((poly[i].X - p.X) * (poly[i].X - p.X) + + (poly[i].Y - p.Y) * (poly[i].Y - p.Y) <= d) + continue; + result[j] = poly[i]; + p = poly[i]; + j++; + } + p = poly[j - 1]; + if ((poly[0].X - p.X) * (poly[0].X - p.X) + + (poly[0].Y - p.Y) * (poly[0].Y - p.Y) <= d) + j--; + if (j < len) + result.RemoveRange(j, len - j); + return result; + } + //------------------------------------------------------------------------------ + + public static void PolyTreeToPolygons(PolyTree polytree, Polygons polygons) + { + polygons.Clear(); + polygons.Capacity = polytree.Total; + AddPolyNodeToPolygons(polytree, polygons); + } + //------------------------------------------------------------------------------ + + public static void AddPolyNodeToPolygons(PolyNode polynode, Polygons polygons) + { + if (polynode.Contour.Count > 0) + polygons.Add(polynode.Contour); + foreach (PolyNode pn in polynode.Childs) + AddPolyNodeToPolygons(pn, polygons); + } + //------------------------------------------------------------------------------ + + + } //end ClipperLib namespace + + class ClipperException : Exception + { + public ClipperException(string description) : base(description){} + } + //------------------------------------------------------------------------------ +} diff --git a/clipper/C#/clipper_library/clipper_library.csproj b/clipper/C#/clipper_library/clipper_library.csproj new file mode 100755 index 0000000..7810468 --- /dev/null +++ b/clipper/C#/clipper_library/clipper_library.csproj @@ -0,0 +1,54 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {9B062971-A88E-4A3D-B3C9-12B78D15FA66} + Library + Properties + ClipperLib + clipper_library + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/clipper/Delphi/agg demo/about_agg.txt b/clipper/Delphi/agg demo/about_agg.txt new file mode 100755 index 0000000..1e231cb --- /dev/null +++ b/clipper/Delphi/agg demo/about_agg.txt @@ -0,0 +1 @@ +http://www.antigrain.com/ \ No newline at end of file diff --git a/clipper/Delphi/agg demo/agg_conv_clipper.pas b/clipper/Delphi/agg demo/agg_conv_clipper.pas new file mode 100755 index 0000000..e983339 --- /dev/null +++ b/clipper/Delphi/agg demo/agg_conv_clipper.pas @@ -0,0 +1,343 @@ +(******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 1.2 * +* Date : 29 September 2011 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2011 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +*******************************************************************************) + +unit + agg_conv_clipper ; + +interface + +{$I agg_mode.inc } + +uses + types, + math, + agg_basics , + agg_array , + agg_vertex_source , + clipper; + +type + clipper_op_e = ( + clipper_or , + clipper_and , + clipper_xor , + clipper_a_minus_b , + clipper_b_minus_a + ); + + clipper_polyFillType = ( + clipper_evenOdd, + clipper_nonZero, + clipper_positive, + clipper_negative + ); + + status = (status_move_to, status_line_to, status_stop ); + + conv_clipper_ptr = ^conv_clipper; + conv_clipper = object(vertex_source) + m_src_a , + m_src_b : vertex_source_ptr; + + m_status : status; + m_vertex , + m_contour : int; + m_operation : clipper_op_e; + + m_scaling_factor: int; + + m_subjFillType, + m_clipFillType: clipper_polyFillType; + + m_poly_a , + m_poly_b , + m_result : TPolygons; + + m_vertex_accumulator: pod_deque; + clipper: TClipper; + + constructor Construct(a, b : vertex_source_ptr; + op : clipper_op_e = clipper_or; + subjFillType: clipper_polyFillType = clipper_evenOdd; + clipFillType: clipper_polyFillType = clipper_evenOdd; + scaling_factor: integer = 2); //default scaling == 2 decimal places + + destructor Destruct; virtual; + + procedure set_source1(source : vertex_source_ptr; + subjFillType: clipper_polyFillType = clipper_evenOdd); + procedure set_source2(source : vertex_source_ptr; + clipFillType: clipper_polyFillType = clipper_evenOdd); + + procedure operation(v : clipper_op_e ); + + // Vertex Source Interface + procedure rewind(path_id : unsigned ); virtual; + function vertex(x ,y : double_ptr ) : unsigned; virtual; + + function next_contour : boolean; + function next_vertex(x ,y : double_ptr ) : boolean; + procedure start_extracting; + procedure start_contour; + procedure add_vertex_ (x,y: double ); + procedure end_contour(var p: TPolygons); + procedure add(src : vertex_source_ptr; var p: TPolygons); + end; + +implementation + +function pft(cpft: clipper_polyFillType): TPolyFillType; +begin + case cpft of + clipper_evenOdd: result := pftEvenOdd; + clipper_nonZero: result := pftNonZero; + clipper_positive: result := pftPositive; + else {clipper_negative: } result := pftNegative; + end; +end; + +constructor conv_clipper.Construct(a, b: vertex_source_ptr; + op: clipper_op_e = clipper_or; + subjFillType: clipper_polyFillType = clipper_evenOdd; + clipFillType: clipper_polyFillType = clipper_evenOdd; + scaling_factor: integer = 2); //default scaling == 2 decimal places +begin + m_src_a := a; + m_src_b := b; + m_operation := op; + + m_scaling_factor := max(min(scaling_factor, 6),0); + m_scaling_factor := round(power(10, m_scaling_factor)); + + m_status := status_move_to; + m_vertex := -1; + m_contour := -1; + + m_poly_a := nil; + m_poly_b := nil; + m_result := nil; + m_vertex_accumulator.Construct (sizeof(TIntPoint), 8 ); + + m_subjFillType := subjFillType; + m_clipFillType := clipFillType; + clipper := TClipper.Create; +end; +//------------------------------------------------------------------------------ + +destructor conv_clipper.Destruct; +begin + clipper.Free; + m_vertex_accumulator.Destruct; +end; +//------------------------------------------------------------------------------ + +procedure conv_clipper.set_source1(source : vertex_source_ptr; + subjFillType: clipper_polyFillType = clipper_evenOdd); +begin + m_src_a := source; + m_subjFillType := subjFillType; +end; +//------------------------------------------------------------------------------ + +procedure conv_clipper.set_source2(source : vertex_source_ptr; + clipFillType: clipper_polyFillType = clipper_evenOdd); +begin + m_src_b := source; + m_clipFillType := clipFillType; +end; +//------------------------------------------------------------------------------ + +procedure conv_clipper.operation(v : clipper_op_e ); +begin + m_operation := v; +end; +//------------------------------------------------------------------------------ + +procedure conv_clipper.start_extracting; +begin + m_status := status_move_to; + m_contour := -1; + m_vertex := -1; +end; +//------------------------------------------------------------------------------ + +procedure conv_clipper.rewind(path_id : unsigned ); +begin + m_src_a.rewind(path_id ); + m_src_b.rewind(path_id ); + + add(m_src_a, m_poly_a ); + add(m_src_b, m_poly_b ); + m_result := nil; + + with clipper do + begin + clear; + case m_operation of + clipper_or : + begin + AddPolygons(m_poly_a, ptSubject); + AddPolygons(m_poly_b, ptClip); + Execute(ctUnion, m_result, pft(m_subjFillType), pft(m_clipFillType)); + end; + clipper_and : + begin + AddPolygons(m_poly_a, ptSubject); + AddPolygons(m_poly_b, ptClip); + Execute(ctIntersection, m_result, pft(m_subjFillType), pft(m_clipFillType)); + end; + clipper_xor : + begin + AddPolygons(m_poly_a, ptSubject); + AddPolygons(m_poly_b, ptClip); + Execute(ctXor, m_result, pft(m_subjFillType), pft(m_clipFillType)); + end; + clipper_a_minus_b : + begin + AddPolygons(m_poly_a, ptSubject); + AddPolygons(m_poly_b, ptClip); + Execute(ctDifference, m_result, pft(m_subjFillType), pft(m_clipFillType)); + end; + clipper_b_minus_a : + begin + AddPolygons(m_poly_b, ptSubject); + AddPolygons(m_poly_a, ptClip); + Execute(ctDifference, m_result, pft(m_subjFillType), pft(m_clipFillType)); + end; + end; + end; + start_extracting; +end; +//------------------------------------------------------------------------------ + +procedure conv_clipper.start_contour; +begin + m_vertex_accumulator.remove_all; +end; +//------------------------------------------------------------------------------ + +procedure conv_clipper.end_contour(var p: TPolygons); +var + i, len: integer; +begin + if m_vertex_accumulator.size < 3 then exit; + len := length(p); + setLength(p, len+1); + setLength(p[len], m_vertex_accumulator.size); + for i := 0 to m_vertex_accumulator.size -1 do + p[len][i] := PIntPoint(m_vertex_accumulator.array_operator(i))^; +end; +//------------------------------------------------------------------------------ + +procedure conv_clipper.add_vertex_ (x,y: double); +var + v : TIntPoint; +begin + v.x := round(x * m_scaling_factor); + v.y := round(y * m_scaling_factor); + m_vertex_accumulator.add(@v); +end; +//------------------------------------------------------------------------------ + +function conv_clipper.next_contour; +begin + result:=false; + inc(m_contour ); + if m_contour >= length(m_result) then exit; + m_vertex:=-1; + result:=true; +end; +//------------------------------------------------------------------------------ + +function conv_clipper.next_vertex(x ,y : double_ptr ) : boolean; +begin + result:=false; + inc(m_vertex); + if m_vertex >= length(m_result[m_contour]) then exit; + x^ := m_result[m_contour][m_vertex].X / m_scaling_factor; + y^ := m_result[m_contour][m_vertex].Y / m_scaling_factor; + result := true; +end; +//------------------------------------------------------------------------------ + +function conv_clipper.vertex(x ,y : double_ptr ) : unsigned; +begin + if m_status = status_move_to then + begin + if next_contour then + begin + if next_vertex(x ,y ) then + begin + m_status:=status_line_to; + result := path_cmd_move_to; + end else + begin + m_status := status_stop; + result := path_cmd_end_poly or path_flags_close; + end; + end else + result := path_cmd_stop; + end else + begin + if next_vertex(x ,y ) then + begin + result := path_cmd_line_to; + end else + begin + m_status := status_move_to; + result := path_cmd_end_poly or path_flags_close; + end; + end; +end; +//------------------------------------------------------------------------------ + +procedure conv_clipper.add(src : vertex_source_ptr; var p: TPolygons); +var + cmd: unsigned; + x, y, start_x ,start_y: double; + starting_first_line : boolean; +begin + start_x := 0.0; + start_y := 0.0; + starting_first_line := true; + p := nil; + + cmd := src.vertex(@x, @y ); + while not is_stop(cmd) do + begin + if is_vertex(cmd) then + begin + if is_move_to(cmd ) then + begin + if not starting_first_line then end_contour(p); + start_contour; + start_x := x; + start_y := y; + end; + add_vertex_(x ,y ); + starting_first_line := false; + end + else if is_end_poly(cmd ) then + begin + if not starting_first_line and is_closed(cmd ) then + add_vertex_(start_x ,start_y ); + end; + cmd := src.vertex(@x ,@y ); + end; + end_contour(p); +end; +//------------------------------------------------------------------------------ + +end. + diff --git a/clipper/Delphi/agg demo/clipper_test.dpr b/clipper/Delphi/agg demo/clipper_test.dpr new file mode 100755 index 0000000..ee60a42 --- /dev/null +++ b/clipper/Delphi/agg demo/clipper_test.dpr @@ -0,0 +1,975 @@ +{target:win} +// +// AggPas 2.4 RM3 Demo application +// Note: Press F1 key on run to see more info about this demo +// +// Paths: src;src\ctrl;src\svg;src\util;src\platform\win;expat-wrap +// +program + clipper_test ; + +uses + SysUtils , + + agg_basics , + agg_platform_support , + + agg_color , + agg_pixfmt , + agg_pixfmt_rgb , + + agg_ctrl , + agg_slider_ctrl , + agg_cbox_ctrl , + agg_rbox_ctrl , + + agg_rendering_buffer , + agg_renderer_base , + agg_renderer_scanline , + agg_renderer_primitives , + agg_rasterizer_scanline_aa , + agg_scanline , + agg_scanline_u , + agg_scanline_p , + agg_render_scanlines , + + agg_math_stroke , + agg_path_storage , + agg_span_solid , + agg_conv_curve , + agg_conv_stroke , + agg_conv_transform , + agg_conv_clip_polygon , + agg_gsv_text , + agg_trans_affine , + agg_vertex_source , + + make_gb_poly_ , + make_arrows_, + + agg_conv_clipper, + clipper in '../clipper.pas'; + +{$I agg_mode.inc } + +const + flip_y = true; + +type + spiral = object(vertex_source ) + m_x , + m_y , + m_r1 , + m_r2 , + m_step , + + m_start_angle , + + m_angle , + m_curr_r , + m_da , + m_dr : double; + m_start : boolean; + + constructor Construct(x ,y ,r1 ,r2 ,step : double; start_angle : double = 0 ); + + procedure rewind(path_id : unsigned ); virtual; + function vertex(x ,y : double_ptr ) : unsigned; virtual; + + end; + + conv_poly_counter = object(vertex_source ) + m_src : vertex_source_ptr; + + m_contours , + m_points : unsigned; + + constructor Construct(src : vertex_source_ptr ); + + procedure rewind(path_id : unsigned ); virtual; + function vertex(x ,y : double_ptr ) : unsigned; virtual; + + end; + + the_application = object(platform_support ) + m_polygons , + m_operation : rbox_ctrl; + + m_x , + m_y : double; + + constructor Construct(format_ : pix_format_e; flip_y_ : boolean ); + destructor Destruct; + + procedure perform_rendering( + sl : scanline_ptr; + ras : rasterizer_scanline_ptr; + ren : renderer_scanline_ptr; + clipper : conv_clipper_ptr ); + + function render_clipper(sl : scanline_ptr; ras : rasterizer_scanline_ptr ) : unsigned; + + procedure on_init; virtual; + procedure on_draw; virtual; + + procedure on_mouse_move (x ,y : int; flags : unsigned ); virtual; + procedure on_mouse_button_down(x ,y : int; flags : unsigned ); virtual; + + procedure on_key(x ,y : int; key ,flags : unsigned ); virtual; + + procedure stress_test; + + end; + +{ CONSTRUCT } +constructor spiral.Construct; +begin + m_x :=x; + m_y :=y; + m_r1:=r1; + m_r2:=r2; + + m_step :=step; + m_start_angle:=start_angle; + m_angle :=start_angle; + + m_da:=deg2rad(4.0 ); + m_dr:=m_step / 90.0; + +end; + +{ REWIND } +procedure spiral.rewind; +begin + m_angle :=m_start_angle; + m_curr_r:=m_r1; + m_start :=true; + +end; + +{ VERTEX } +function spiral.vertex; +begin + if m_curr_r > m_r2 then + begin + result:=path_cmd_stop; + + exit; + + end; + + x^:=m_x + Cos(m_angle ) * m_curr_r; + y^:=m_y + Sin(m_angle ) * m_curr_r; + + m_curr_r:=m_curr_r + m_dr; + m_angle :=m_angle + m_da; + + if m_start then + begin + m_start:=false; + + result:=path_cmd_move_to; + + end + else + result:=path_cmd_line_to; + +end; + +{ CONSTRUCT } +constructor conv_poly_counter.Construct; +begin + m_src:=src; + + m_contours:=0; + m_points :=0; + +end; + +{ REWIND } +procedure conv_poly_counter.rewind; +begin + m_contours:=0; + m_points :=0; + + m_src.rewind(path_id ); + +end; + +{ VERTEX } +function conv_poly_counter.vertex; +var + cmd : unsigned; + +begin + cmd:=m_src.vertex(x ,y ); + + if is_vertex(cmd ) then + inc(m_points ); + + if is_move_to(cmd ) then + inc(m_contours ); + + result:=cmd; + +end; + +{ CONSTRUCT } +constructor the_application.Construct; +begin + inherited Construct(format_ ,flip_y_ ); + + m_polygons.Construct (5.0 ,5.0 ,5.0 + 205.0 ,110.0 ,not flip_y_ ); + m_operation.Construct(555.0 ,5.0 ,555.0 + 80.0 ,130.0 ,not flip_y_ ); + + m_operation.add_item ('None' ); + m_operation.add_item ('OR' ); + m_operation.add_item ('AND' ); + m_operation.add_item ('XOR' ); + m_operation.add_item ('A-B' ); + m_operation.add_item ('B-A' ); + m_operation.cur_item_(2 ); + + add_ctrl(@m_operation ); + + m_polygons.add_item ('Two Simple Paths' ); + m_polygons.add_item ('Closed Stroke' ); + m_polygons.add_item ('Great Britain and Arrows' ); + m_polygons.add_item ('Great Britain and Spiral' ); + m_polygons.add_item ('Spiral and Glyph' ); + m_polygons.cur_item_(3 ); + + add_ctrl(@m_polygons ); + +end; + +{ DESTRUCT } +destructor the_application.Destruct; +begin + inherited Destruct; + + m_polygons.Destruct; + m_operation.Destruct; + +end; + +{ PERFORM_RENDERING } +procedure the_application.perform_rendering; +var + counter : conv_poly_counter; + + t1 ,t2 ,x ,y : double; + + cmd : unsigned; + buf : array[0..99 ] of char8; + + rgba : aggclr; + txt : gsv_text; + + txt_stroke : conv_stroke; + +begin + if m_operation._cur_item > 0 then + begin + // Render clipped polygon + ras.reset; + + case m_operation._cur_item of + 1: clipper.operation(clipper_or ); + 2: clipper.operation(clipper_and ); + 3: clipper.operation(clipper_xor ); + 4: clipper.operation(clipper_a_minus_b ); + 5: clipper.operation(clipper_b_minus_a ); + + end; + + counter.Construct(clipper); + + start_timer; + counter.rewind(0); /////////////////////////this is where it all happens! + t1:=elapsed_time; + + ras.reset; + start_timer; + + cmd:=counter.vertex(@x ,@y ); + + while not is_stop(cmd ) do + begin + ras.add_vertex(x ,y ,cmd ); + + cmd:=counter.vertex(@x ,@y ); + + end; + rgba.ConstrDbl (0.5 ,0.75 ,0.5 ,1.0 ); + ren.color_ (@rgba ); + render_scanlines(ras ,sl ,ren ); + + t2:=elapsed_time; + + // Render information text + sprintf(@buf[0 ] ,'Contours: %d ' ,counter.m_contours ); + sprintf(@buf[StrLen(@buf ) ] ,'Points: %d' ,counter.m_points ); + + txt.Construct; + txt_stroke.Construct(@txt ); + + txt_stroke.width_ (1.5 ); + txt_stroke.line_cap_(round_cap ); + txt.size_ (10.0 ); + txt.start_point_ (250 ,5 ); + txt.text_ (@buf[0 ] ); + + ras.add_path (@txt_stroke ); + rgba.ConstrDbl (0.0 ,0.0 ,0.0 ); + ren.color_ (@rgba ); + render_scanlines(ras ,sl ,ren ); + + sprintf(@buf[0 ] ,'Clipper=%.3fms ' ,t1 ); + sprintf(@buf[StrLen(buf ) ] ,'Render=%.3fms' ,t2 ); + + txt.start_point_(250 ,20 ); + txt.text_ (@buf[0 ] ); + + ras.add_path (@txt_stroke ); + rgba.ConstrDbl (0.0 ,0.0 ,0.0 ); + ren.color_ (@rgba ); + render_scanlines(ras ,sl ,ren ); + + // Free + txt.Destruct; + txt_stroke.Destruct; + + end; + +end; + +{ RENDER_CLIPPER } +function the_application.render_clipper; +var + pf : pixel_formats; + rb : renderer_base; + + ren : renderer_scanline_aa_solid; + ps1 , + ps2 , + + gb_poly , + arrows , + glyph : path_storage; + + rgba : aggclr; + x ,y : double; + + mtx1 , + mtx2 , + mtx : trans_affine; + tat : trans_affine_translation; + tas : trans_affine_scaling; + + stroke, + stroke_stroke, + stroke_gb_poly : conv_stroke; + + trans , + trans_gb_poly , + trans_arrows : conv_transform; + + curve : conv_curve; + + sp : spiral; + clipper : conv_clipper; + +begin + pixfmt_bgr24(pf ,rbuf_window ); + + rb.Construct (@pf ); + ren.Construct(@rb ); + + case m_polygons._cur_item of + 0 : // Two simple paths + begin + ps1.Construct; + ps2.Construct; + + clipper.Construct(@ps1, @ps2, clipper_or, + clipper_nonZero, clipper_nonZero); + + x:=m_x - _initial_width / 2 + 100; + y:=m_y - _initial_height / 2 + 100; + + ps1.move_to(x + 140 ,y + 145 ); + ps1.line_to(x + 225 ,y + 44 ); + //ps1.line_to(x + 296 ,y + 219 ); + ps1.line_to(x + 396 ,y + 319 ); + ps1.close_polygon; + + ps1.line_to(x + 226 ,y + 289 ); + ps1.line_to(x + 82 ,y + 292 ); + + ps1.move_to(x + 220 ,y + 222 ); + ps1.line_to(x + 363 ,y + 249 ); + ps1.line_to(x + 265 ,y + 331 ); + + ps1.move_to(x + 242 ,y + 243 ); + ps1.line_to(x + 268 ,y + 309 ); + ps1.line_to(x + 325 ,y + 261 ); + + ps1.move_to(x + 259 ,y + 259 ); + ps1.line_to(x + 273 ,y + 288 ); + ps1.line_to(x + 298 ,y + 266 ); + + ps2.move_to(100 + 32 ,100 + 77 ); + ps2.line_to(100 + 473 ,100 + 263 ); + ps2.line_to(100 + 351 ,100 + 290 ); + ps2.line_to(100 + 354 ,100 + 374 ); + + ras.reset; + ras.add_path (@ps1 ); + rgba.ConstrDbl (0 ,0 ,0 ,0.1 ); + ren.color_ (@rgba ); + render_scanlines(ras ,sl ,@ren ); + + ras.reset; + ras.add_path (@ps2 ); + rgba.ConstrDbl (0.5 ,0.5 ,0 ,0.1 ); + ren.color_ (@rgba ); + render_scanlines(ras ,sl ,@ren ); + + perform_rendering(sl ,ras ,@ren ,@clipper ); + + ps1.Destruct; + ps2.Destruct; + clipper.Destruct; + + end; + + 1 : // Closed stroke + begin + ps1.Construct; + ps2.Construct; + stroke.Construct(@ps2 ); + stroke.width_ (10.0 ); + + clipper.Construct(@ps1 ,@stroke, clipper_or, + clipper_nonZero, clipper_nonZero); + + x:=m_x - _initial_width / 2 + 100; + y:=m_y - _initial_height / 2 + 100; + + ps1.move_to(x + 140 ,y + 145 ); + ps1.line_to(x + 225 ,y + 44 ); + ps1.line_to(x + 296 ,y + 219 ); + ps1.close_polygon; + + ps1.line_to(x + 226 ,y + 289 ); + ps1.line_to(x + 82 ,y + 292 ); + + ps1.move_to(x + 220 - 50 ,y + 222 ); + ps1.line_to(x + 265 - 50 ,y + 331 ); + ps1.line_to(x + 363 - 50 ,y + 249 ); + ps1.close_polygon(path_flags_ccw ); + + ps2.move_to(100 + 32 ,100 + 77 ); + ps2.line_to(100 + 473 ,100 + 263 ); + ps2.line_to(100 + 351 ,100 + 290 ); + ps2.line_to(100 + 354 ,100 + 374 ); + ps2.close_polygon; + + ras.reset; + ras.add_path (@ps1 ); + rgba.ConstrDbl (0 ,0 ,0 ,0.1 ); + ren.color_ (@rgba ); + render_scanlines(ras ,sl ,@ren ); + + ras.reset; + ras.add_path (@stroke ); + rgba.ConstrDbl (0.5 ,0.5 ,0 ,0.1 ); + ren.color_ (@rgba ); + render_scanlines(ras ,sl ,@ren ); + + perform_rendering(sl ,ras ,@ren ,@clipper ); + + ps1.Destruct; + ps2.Destruct; + stroke.Destruct; + clipper.Destruct; + + end; + + 2 : // Great Britain and Arrows + begin + gb_poly.Construct; + arrows.Construct; + + make_gb_poly(@gb_poly ); + make_arrows (@arrows ); + + mtx1.Construct; + mtx2.Construct; + tat.Construct(-1150 ,-1150 ); + tas.Construct(2.0 ); + mtx1.multiply(@tat ); + mtx1.multiply(@tas ); + + mtx2:=mtx1; + + tat.Construct(m_x - _initial_width / 2 ,m_y - _initial_height / 2 ); + + mtx2.multiply(@tat ); + + trans_gb_poly.Construct(@gb_poly ,@mtx1 ); + trans_arrows.Construct (@arrows ,@mtx2 ); + + clipper.Construct(@trans_gb_poly ,@trans_arrows, + clipper_or, clipper_nonZero, clipper_nonZero); + + ras.add_path (@trans_gb_poly ); + rgba.ConstrDbl (0.5 ,0.5 ,0 ,0.1 ); + ren.color_ (@rgba ); + render_scanlines(ras ,sl ,@ren ); + + stroke_gb_poly.Construct(@trans_gb_poly ); + stroke_gb_poly.width_ (0.1); + ras.add_path (@stroke_gb_poly ); + rgba.ConstrDbl (0 ,0 ,0 ); + ren.color_ (@rgba ); + render_scanlines (ras ,sl ,@ren ); + + ras.add_path (@trans_arrows ); + rgba.ConstrDbl (0.0 ,0.5 ,0.5 ,0.1 ); + ren.color_ (@rgba ); + render_scanlines(ras ,sl ,@ren ); + + perform_rendering(sl ,ras ,@ren ,@clipper ); + + gb_poly.Destruct; + arrows.Destruct; + stroke_gb_poly.Destruct; + clipper.Destruct; + + end; + + 3 : // Great Britain and a Spiral + begin + sp.Construct (m_x ,m_y ,10 ,150 ,30 ,0.0 ); + stroke.Construct(@sp ); + stroke.width_ (15.0 ); + + gb_poly.Construct; + make_gb_poly(@gb_poly ); + + mtx.Construct; + tat.Construct(-1150 ,-1150 ); + tas.Construct(2.0 ); + mtx.multiply(@tat ); + mtx.multiply(@tas ); + + trans_gb_poly.Construct(@gb_poly ,@mtx ); + + clipper.Construct(@trans_gb_poly ,@stroke, + clipper_or, clipper_nonZero, clipper_nonZero); + + ras.add_path (@trans_gb_poly ); + rgba.ConstrDbl (0.5 ,0.5 ,0 ,0.1 ); + ren.color_ (@rgba ); + render_scanlines(ras ,sl ,@ren ); + + stroke_gb_poly.Construct(@trans_gb_poly ); + stroke_gb_poly.width_ (0.2 ); + ras.add_path (@stroke_gb_poly ); + rgba.ConstrDbl (0 ,0 ,0 ); + ren.color_ (@rgba ); + render_scanlines (ras ,sl ,@ren ); + + ras.add_path (@stroke ); + rgba.ConstrDbl (0.0 ,0.5 ,0.5 ,0.1 ); + ren.color_ (@rgba ); + render_scanlines(ras ,sl ,@ren ); + + stroke_stroke.Construct(@stroke); + stroke_stroke.width_(0.1); + ras.add_path(@stroke_stroke ); + rgba.ConstrDbl (0 ,0 ,0 ); + ren.color_ (@rgba ); + render_scanlines (ras ,sl ,@ren ); + + perform_rendering(sl ,ras ,@ren ,@clipper ); + + stroke.Destruct; + stroke_stroke.Destruct; + gb_poly.Destruct; + stroke_gb_poly.Destruct; + clipper.Destruct; + + end; + + 4 : // Spiral and glyph + begin + sp.Construct (m_x ,m_y ,10 ,150 ,30 ,0.0 ); + stroke.Construct(@sp ); + stroke.width_ (15.0 ); + + glyph.Construct; + glyph.move_to(28.47 ,6.45 ); + glyph.curve3 (21.58 ,1.12 ,19.82 ,0.29 ); + glyph.curve3 (17.19 ,-0.93 ,14.21 ,-0.93 ); + glyph.curve3 (9.57 ,-0.93 ,6.57 ,2.25 ); + glyph.curve3 (3.56 ,5.42 ,3.56 ,10.60 ); + glyph.curve3 (3.56 ,13.87 ,5.03 ,16.26 ); + glyph.curve3 (7.03 ,19.58 ,11.99 ,22.51 ); + glyph.curve3 (16.94 ,25.44 ,28.47 ,29.64 ); + glyph.line_to(28.47 ,31.40 ); + glyph.curve3 (28.47 ,38.09 ,26.34 ,40.58 ); + glyph.curve3 (24.22 ,43.07 ,20.17 ,43.07 ); + glyph.curve3 (17.09 ,43.07 ,15.28 ,41.41 ); + glyph.curve3 (13.43 ,39.75 ,13.43 ,37.60 ); + glyph.line_to(13.53 ,34.77 ); + glyph.curve3 (13.53 ,32.52 ,12.38 ,31.30 ); + glyph.curve3 (11.23 ,30.08 ,9.38 ,30.08 ); + glyph.curve3 (7.57 ,30.08 ,6.42 ,31.35 ); + glyph.curve3 (5.27 ,32.62 ,5.27 ,34.81 ); + glyph.curve3 (5.27 ,39.01 ,9.57 ,42.53 ); + glyph.curve3 (13.87 ,46.04 ,21.63 ,46.04 ); + glyph.curve3 (27.59 ,46.04 ,31.40 ,44.04 ); + glyph.curve3 (34.28 ,42.53 ,35.64 ,39.31 ); + glyph.curve3 (36.52 ,37.21 ,36.52 ,30.71 ); + glyph.line_to(36.52 ,15.53 ); + glyph.curve3 (36.52 ,9.13 ,36.77 ,7.69 ); + glyph.curve3 (37.01 ,6.25 ,37.57 ,5.76 ); + glyph.curve3 (38.13 ,5.27 ,38.87 ,5.27 ); + glyph.curve3 (39.65 ,5.27 ,40.23 ,5.62 ); + glyph.curve3 (41.26 ,6.25 ,44.19 ,9.18 ); + glyph.line_to(44.19 ,6.45 ); + glyph.curve3 (38.72 ,-0.88 ,33.74 ,-0.88 ); + glyph.curve3 (31.35 ,-0.88 ,29.93 ,0.78 ); + glyph.curve3 (28.52 ,2.44 ,28.47 ,6.45 ); + glyph.close_polygon; + + glyph.move_to(28.47 ,9.62 ); + glyph.line_to(28.47 ,26.66 ); + glyph.curve3 (21.09 ,23.73 ,18.95 ,22.51 ); + glyph.curve3 (15.09 ,20.36 ,13.43 ,18.02 ); + glyph.curve3 (11.77 ,15.67 ,11.77 ,12.89 ); + glyph.curve3 (11.77 ,9.38 ,13.87 ,7.06 ); + glyph.curve3 (15.97 ,4.74 ,18.70 ,4.74 ); + glyph.curve3 (22.41 ,4.74 ,28.47 ,9.62 ); + glyph.close_polygon; + + mtx.Construct; + tas.Construct(4.0 ); + tat.Construct(220 ,200 ); + mtx.multiply(@tas ); + mtx.multiply(@tat ); + + trans.Construct(@glyph ,@mtx ); + curve.Construct(@trans ); + + clipper.Construct(@stroke ,@curve, clipper_or, + clipper_nonZero, clipper_nonZero); + + ras.reset; + ras.add_path (@stroke ); + rgba.ConstrDbl (0 ,0 ,0 ,0.1 ); + ren.color_ (@rgba ); + render_scanlines(ras ,sl ,@ren ); + + ras.reset; + ras.add_path (@curve ); + rgba.ConstrDbl (0.5 ,0.5 ,0 ,0.1 ); + ren.color_ (@rgba ); + render_scanlines(ras ,sl ,@ren ); + + perform_rendering(sl ,ras ,@ren ,@clipper ); + + stroke.Destruct; + glyph.Destruct; + curve.Destruct; + clipper.Destruct; + + end; + + end; + + result:=0; + +end; + +{ ON_INIT } +procedure the_application.on_init; +begin + m_x:=_width / 2.0; + m_y:=_height / 2.0; + +end; + +{ ON_DRAW } +procedure the_application.on_draw; +var + pf : pixel_formats; + + rgba : aggclr; + + ren_base : renderer_base; + ren_solid : renderer_scanline_aa_solid; + + sl : scanline_u8; + ras : rasterizer_scanline_aa; + +begin +// Initialize structures + pixfmt_bgr24(pf ,rbuf_window ); + + ren_base.Construct (@pf ); + ren_solid.Construct(@ren_base ); + + rgba.ConstrDbl(1 ,1 ,1 ); + ren_base.clear(@rgba ); + + sl.Construct; + ras.Construct; + +// Render + render_clipper(@sl ,@ras ); + +// Render the controls + render_ctrl(@ras ,@sl ,@ren_solid ,@m_polygons ); + render_ctrl(@ras ,@sl ,@ren_solid ,@m_operation ); + +// Free AGG resources + sl.Destruct; + ras.Destruct; + +end; + +{ ON_MOUSE_MOVE } +procedure the_application.on_mouse_move; +begin + if flags and mouse_left <> 0 then + begin + m_x:=x; + m_y:=y; + + force_redraw; + + end; + +end; + +{ ON_MOUSE_BUTTON_DOWN } +procedure the_application.on_mouse_button_down; +var + buf : array[0..99 ] of char8; + +begin + if flags and mouse_left <> 0 then + begin + m_x:=x; + m_y:=y; + + force_redraw; + + end; + + if flags and mouse_right <> 0 then + begin + sprintf (@buf[0 ] ,'%d ' ,x ); + sprintf (@buf[StrLen(@buf ) ] ,'%d' ,y ); + message_(@buf[0 ] ); + + end; + +end; + +{ ON_KEY } +procedure the_application.on_key; +begin + case key of + byte('t' ) , + byte('T' ) : + stress_test; + + end; + + if key = key_f1 then + message_( + '''Clipper'' by Angus Johnson is the most reliable implementation of the '#13 + + 'polygon boolean algebra. It implements Bala R. Vatti''s algorithm of arbitrary '#13 + + 'polygon clipping and allows you to calculate the Union, Intersection, Difference, '#13 + + 'and Exclusive OR between two poly-polygons (i.e., polygonal areas consisted of '#13 + + 'several contours). AGG has a simple wrapper class that can be used in the '#13 + + 'coordinate conversion pipeline. Note that all operations are done '#13 + + 'in the vectorial representation of the contours before rendering.'#13#13 + + 'How to play with:'#13#13 + + 'You can drag one polygon with the left mouse button pressed.'#13 + + 'Press the "T" key to perform the random polygon clipping stress testing.'#13 + + '(may take some time)' + + #13#13'Note: F2 key saves current "screenshot" file in this demo''s directory. ' ); + +end; + +{ STRESS_TEST } +// Stress-test. +// Works quite well on random polygons, no crashes, no memory leaks! +// Sometimes takes long to produce the result +procedure the_application.stress_test; + +{ random } +function random(min ,max : double ) : double; +var + r : int; + +begin + r:=(System.Random($7fff ) shl 15 ) or System.Random($7fff ); + + result:=$FFFFFFF + 1; + result:=((r and $FFFFFFF) / result ) * (max - min ) + min; + +end; + +var + sl : scanline_u8; + ras : rasterizer_scanline_aa; + + pf : pixel_formats; + + ren_base : renderer_base; + ren_solid : renderer_scanline_aa_solid; + + ps1 ,ps2 : path_storage; + + clipper : conv_clipper; + rgba : aggclr; + + i ,num_poly1 ,num_poly2 ,j ,k ,np ,op: unsigned; + + buf : array[0..99 ] of char8; + txt : gsv_text; + + txt_stroke : conv_stroke; + +begin + sl.Construct; + ras.Construct; + + pixfmt_bgr24(pf ,rbuf_window ); + + ren_base.Construct (@pf ); + ren_solid.Construct(@ren_base ); + + ps1.Construct; + ps2.Construct; + clipper.Construct(@ps1 ,@ps2, clipper_or, clipper_nonZero, clipper_nonZero); + + txt.Construct; + txt_stroke.Construct(@txt ); + + txt_stroke.width_ (1.5 ); + txt_stroke.line_cap_(round_cap ); + txt.size_ (10.0 ); + txt.start_point_ (5 ,5 ); + + for i:=0 to 999 do + begin + rgba.ConstrDbl(1 ,1 ,1 ); + ren_base.clear(@rgba ); + + num_poly1:=System.Random($7fff ) mod 10 + 1; + num_poly2:=System.Random($7fff ) mod 10 + 1; + + ps1.remove_all; + ps2.remove_all; + + for j:=0 to num_poly1 - 1 do + begin + ps1.move_to(random(0 ,_width ) ,random(0 ,_height ) ); + + np:=System.Random($7fff ) mod 20 + 2; + + for k:=0 to np - 1 do + ps1.line_to(random(0 ,_width ) ,random(0 ,_height ) ); + + end; + + for j:=0 to num_poly2 - 1 do + begin + ps2.move_to(random(0 ,_width ) ,random(0 ,_height ) ); + + np:=System.Random($7fff ) mod 20 + 2; + + for k:=0 to np - 1 do + ps2.line_to(random(0 ,_width ) ,random(0 ,_height ) ); + + end; + + op:=System.Random($7fff ) mod 5; + + case op of + 0 : + clipper.operation(clipper_or ); + + 1 : + clipper.operation(clipper_and ); + + 2 : + clipper.operation(clipper_xor ); + + 3 : + clipper.operation(clipper_a_minus_b ); + + else + clipper.operation(clipper_b_minus_a ); + + end; + + // Clipping result + ras.add_path (@clipper ); + rgba.ConstrDbl (0.5 ,0.0 ,0 ,0.5 ); + ren_solid.color_(@rgba ); + render_scanlines(@ras ,@sl ,@ren_solid ); + + // Counter display + sprintf (@buf[0 ] ,'%d / 1000' ,i + 1 ); + txt.text_(@buf[0 ] ); + + txt.start_point_(5 ,5 ); + + ras.add_path (@txt_stroke ); + rgba.ConstrDbl (0.0 ,0.0 ,0.0 ); + ren_solid.color_(@rgba ); + render_scanlines(@ras ,@sl ,@ren_solid ); + + // Refresh + update_window; + + end; + + message_('Done' ); + + ps1.Destruct; + ps2.Destruct; + clipper.Destruct; + + sl.Destruct; + ras.Destruct; + + txt.Destruct; + txt_stroke.Destruct; + + force_redraw; + +end; + +VAR + app : the_application; + +BEGIN + app.Construct(pix_format_bgr24 ,flip_y ); + app.caption_ ('AGG Example. Polygon Clipping with Clipper (F1-Help)' ); + + if app.init(640 ,520 ,window_resize ) then + app.run; + + app.Destruct; + +END. \ No newline at end of file diff --git a/clipper/Delphi/agg demo/clipper_test.exe b/clipper/Delphi/agg demo/clipper_test.exe new file mode 100755 index 0000000..962804d Binary files /dev/null and b/clipper/Delphi/agg demo/clipper_test.exe differ diff --git a/clipper/Delphi/cairo demo/Cairo Resources.txt b/clipper/Delphi/cairo demo/Cairo Resources.txt new file mode 100755 index 0000000..95e2e83 --- /dev/null +++ b/clipper/Delphi/cairo demo/Cairo Resources.txt @@ -0,0 +1,12 @@ +http://cairographics.org/ + +The Windows dynamic linked libraries necessary to run Cairo can be downloaded from +http://www.gtk.org/download/win32.php +All the dlls listed under the heading "Required third party dependencies" are +required except gettext-runtime.dll. + +A Delphi library that interfaces with the Cairo dlls is +"Cairo Graphics Delphi Bindings" available at +http://www.rodi.dk/programming_cairo.php +Direct link to the download - +http://www.rodi.dk/download1.php?cairo-delphi-bindings-1.0.zip diff --git a/clipper/Delphi/cairo demo/CairoClipperDemo1.dpr b/clipper/Delphi/cairo demo/CairoClipperDemo1.dpr new file mode 100755 index 0000000..029006f --- /dev/null +++ b/clipper/Delphi/cairo demo/CairoClipperDemo1.dpr @@ -0,0 +1,206 @@ +program CairoClipperDemo1; + +uses + Windows, + sysutils, + Messages, + Graphics, + Math, + clipper in '../clipper.pas', + Cairo in '../cairo_src/cairo.pas', + CairoWin32 in '../cairo_src/cairowin32.pas', + cairo_clipper in 'cairo_clipper.pas'; + +{$R *.res} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +var + offsetVal: integer = 0; + bmp: graphics.TBitmap; //just to buffer drawing and minimize flicker + +procedure PaintBitmap; +var + surface: Pcairo_surface_t; + cr: Pcairo_t; + extent: cairo_text_extents_t; + clipper: TClipper; + ppa: TPolygons; + rec: TRect; + text: string; +const + scaling = 2; //because Clipper now only accepts integer coordinates +begin + //create a cairo context for the bitmap surface ... + surface := cairo_win32_surface_create(bmp.canvas.handle); + cr := cairo_create(surface); + cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING); + + clipper := TClipper.Create; + + //fill the context background with white ... + cairo_rectangle(cr, 0, 0, bmp.Width, bmp.Height); + cairo_set_source_rgba(cr, 1, 1, 1, 1); + cairo_fill(cr); + + //create a circular pattern, add the path to clipper and then draw it ... + cairo_arc(cr, 165,110,70,0,2*3.1415926); + cairo_close_path(cr); //important because we can only clip polygons + CairoToPointArray(cr, ppa, scaling); + clipper.AddPolygons(ppa, ptSubject); + cairo_set_line_width(cr, 2.0); + cairo_set_source_rgba(cr, 0, 0, 1, 0.25); + cairo_fill_preserve(cr); + cairo_set_source_rgba(cr, 0, 0, 0, 0.5); + cairo_stroke(cr); + cairo_new_path(cr); + + //create a star pattern, add the path to clipper and then draw it ... + cairo_move_to(cr, 60,110); + cairo_line_to(cr, 240,70); + cairo_line_to(cr, 110,210); + cairo_line_to(cr, 140,25); + cairo_line_to(cr, 230,200); + cairo_close_path(cr); + cairo_new_sub_path(cr); + cairo_arc(cr, 185,50,20,0,2*3.1415926); + cairo_close_path(cr); + CairoToPointArray(cr, ppa, scaling); + clipper.AddPolygons(ppa, ptClip); + cairo_set_source_rgba(cr, 1, 0, 0, 0.25); + cairo_fill_preserve(cr); + cairo_set_source_rgba(cr, 0, 0, 0, 0.5); + cairo_stroke(cr); + + //now clip and draw the paths previously added to clipper .... + clipper.Execute(ctIntersection, ppa, pftNonZero, pftNonZero); + + cairo_set_line_width(cr, 2.0); + if offsetVal <> 0 then + ppa := OffsetPolygons(ppa, offsetVal*power(10,scaling), jtRound); + PointArrayToCairo(ppa, cr, scaling); + cairo_set_source_rgba(cr, 1, 1, 0, 1); + cairo_fill_preserve(cr); + cairo_set_source_rgba(cr, 0, 0, 0, 1); + cairo_stroke(cr); + + GetClientRect(GetActiveWindow, rec); + cairo_set_font_size(cr,11); + text := 'Polygon offset = '+ inttostr(offsetVal) + '. (Adjust with arrow keys)'; + cairo_text_extents(cr, pchar(text), @extent); + cairo_move_to(cr, 10, rec.Bottom - extent.height); + cairo_show_text(cr, pchar(text)); + + //clean up ... + cairo_surface_finish(surface); +end; +//------------------------------------------------------------------------------ + +function WndProc(Wnd : HWND; message : UINT; + wParam : Integer; lParam: Integer) : Integer; stdcall; +var + dc: HDC; + ps: PAINTSTRUCT; +begin + case message of + WM_PAINT: + begin + dc := BeginPaint(Wnd, ps); + with bmp do BitBlt(dc,0,0,Width,Height,canvas.Handle,0,0,SRCCOPY); + EndPaint(Wnd, ps); + result := 0; + end; + + WM_CREATE: + begin + bmp := graphics.TBitmap.Create; + result := DefWindowProc(Wnd, message, wParam, lParam); + end; + WM_DESTROY: + begin + bmp.Free; + PostQuitMessage(0); + result := 0; + end; + + WM_SIZE: + begin + bmp.Width := loword(lparam); + bmp.Height := hiword(lparam); + PaintBitmap; + result := DefWindowProc(Wnd, message, wParam, lParam); + end; + + WM_KEYDOWN: + case wParam of + VK_ESCAPE: + begin + PostQuitMessage(0); + result := 0; + end; + VK_RIGHT, VK_UP: + begin + if offsetVal < 20 then inc(offsetVal); + PaintBitmap; + InvalidateRect(0, nil, false); + result := 0; + end; + VK_LEFT, VK_DOWN: + begin + if offsetVal > -20 then dec(offsetVal); + PaintBitmap; + InvalidateRect(0, nil, false); + result := 0; + end; + else + result := DefWindowProc(Wnd, message, wParam, lParam); + end; + + else + result := DefWindowProc(Wnd, message, wParam, lParam); + end; +end; +//------------------------------------------------------------------------------ + +var + hWnd : THandle; + Msg : TMsg; + wndClass : TWndClass; +begin + wndClass.style := CS_HREDRAW or CS_VREDRAW; + wndClass.lpfnWndProc := @WndProc; + wndClass.cbClsExtra := 0; + wndClass.cbWndExtra := 0; + wndClass.hInstance := hInstance; + wndClass.hIcon := LoadIcon(0, IDI_APPLICATION); + wndClass.hCursor := LoadCursor(0, IDC_ARROW); + wndClass.hbrBackground := HBRUSH(GetStockObject(WHITE_BRUSH)); + wndClass.lpszMenuName := nil; + wndClass.lpszClassName := 'CairoClipper'; + + RegisterClass(wndClass); + + hWnd := CreateWindow( + 'CairoClipper', // window class name + 'Cairo-Clipper Demo', // window caption + WS_OVERLAPPEDWINDOW, // window style + Integer(CW_USEDEFAULT), // initial x position + Integer(CW_USEDEFAULT), // initial y position + 400, // initial x size + 300, // initial y size + 0, // parent window handle + 0, // window menu handle + hInstance, // program instance handle + nil); // creation parameters + + ShowWindow(hWnd, SW_SHOW); + UpdateWindow(hWnd); + + while(GetMessage(msg, 0, 0, 0)) do + begin + TranslateMessage(msg); + DispatchMessage(msg); + end; +end. + diff --git a/clipper/Delphi/cairo demo/CairoClipperDemo1.res b/clipper/Delphi/cairo demo/CairoClipperDemo1.res new file mode 100755 index 0000000..d8a5528 Binary files /dev/null and b/clipper/Delphi/cairo demo/CairoClipperDemo1.res differ diff --git a/clipper/Delphi/cairo demo/cairo_clipper.pas b/clipper/Delphi/cairo demo/cairo_clipper.pas new file mode 100755 index 0000000..842e4ef --- /dev/null +++ b/clipper/Delphi/cairo demo/cairo_clipper.pas @@ -0,0 +1,121 @@ +unit cairo_clipper; + +(******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 1.2 * +* Date : 29 September 2011 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2011 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +*******************************************************************************) + +interface + +uses + SysUtils, Classes, Cairo, types, math, clipper; + +//nb: Since Clipper only accepts integer coordinates, fractional values have to +//be scaled up and down when being passed to and from Clipper. This is easily +//accomplished by setting the scaling factor (10^x) in the following functions. +//When scaling, remember that on most platforms, integer is only a 32bit value. +function PointArrayToCairo(const polys: TPolygons; + cairo: Pcairo_t; scaling_factor: integer = 2): boolean; +function CairoToPointArray(cairo: Pcairo_t; + out polys: TPolygons; scaling_factor: integer = 2): boolean; + +implementation + +type + PCairoPathDataArray = ^TCairoPathDataArray; + TCairoPathDataArray = + array [0.. MAXINT div sizeof(cairo_path_data_t) -1] of cairo_path_data_t; + +function PointArrayToCairo(const polys: TPolygons; + cairo: Pcairo_t; scaling_factor: integer = 2): boolean; +var + i,j: integer; + scaling: double; +begin + result := assigned(cairo); + if not result then exit; + if abs(scaling_factor) > 6 then + raise Exception.Create('PointArrayToCairo: invalid scaling factor'); + scaling := power(10, scaling_factor); + for i := 0 to high(polys) do + begin + cairo_new_sub_path(cairo); + for j := 0 to high(polys[i]) do + with polys[i][j] do cairo_line_to(cairo,X/scaling,Y/scaling); + cairo_close_path(cairo); + end; +end; +//------------------------------------------------------------------------------ + +function CairoToPointArray(cairo: Pcairo_t; + out polys: TPolygons; scaling_factor: integer = 2): boolean; +const + buffLen1: integer = 32; + buffLen2: integer = 128; +var + i,currLen1, currLen2: integer; + pdHdr: cairo_path_data_t; + path: Pcairo_path_t; + currPos: TIntPoint; + scaling: double; +begin + if abs(scaling_factor) > 6 then + raise Exception.Create('PointArrayToCairo: invalid scaling factor'); + scaling := power(10, scaling_factor); + result := false; + setlength(polys, buffLen1); + currLen1 := 1; + currLen2 := 0; + currPos := IntPoint(0,0); + i := 0; + path := cairo_copy_path_flat(cairo); + try + while i < path.num_data do + begin + pdHdr := PCairoPathDataArray(path.data)[i]; + case pdHdr.header._type of + CAIRO_PATH_CLOSE_PATH: + begin + if currLen2 > 1 then //ie: ignore if < 3 points (not a polygon) + begin + setlength(polys[currLen1-1], currLen2); + setlength(polys[currLen1], buffLen2); + inc(currLen1); + end; + currLen2 := 0; + currPos := IntPoint(0,0); + result := true; + end; + CAIRO_PATH_MOVE_TO, CAIRO_PATH_LINE_TO: + begin + result := false; + if (pdHdr.header._type = CAIRO_PATH_MOVE_TO) and + (currLen2 > 0) then break; //ie enforce ClosePath for polygons + if (currLen2 mod buffLen2 = 0) then + SetLength(polys[currLen1-1], currLen2 + buffLen2); + with PCairoPathDataArray(path.data)[i+1].point do + currPos := IntPoint(Round(x*scaling),Round(y*scaling)); + polys[currLen1-1][currLen2] := currPos; + inc(currLen2); + end; + end; + inc(i, pdHdr.header.length); + end; + finally + cairo_path_destroy(path); + end; + dec(currLen1); //ie enforces a ClosePath + setlength(polys, currLen1); +end; +//------------------------------------------------------------------------------ + +end. diff --git a/clipper/Delphi/clipper.pas b/clipper/Delphi/clipper.pas new file mode 100755 index 0000000..7575a1e --- /dev/null +++ b/clipper/Delphi/clipper.pas @@ -0,0 +1,3968 @@ +unit clipper; + +(******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 5.1.0 * +* Date : 1 February 2013 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2013 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) PP 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 PP. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************) + +interface + +uses + SysUtils, Types, Classes, Math; + +type + PIntPoint = ^TIntPoint; + TIntPoint = record X, Y: Int64; end; + TIntRect = record Left, Top, Right, Bottom: Int64; end; + + TClipType = (ctIntersection, ctUnion, ctDifference, ctXor); + TPolyType = (ptSubject, ptClip); + //By far the most widely used winding rules for polygon filling are + //EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) + //Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) + //see http://glprogramming.com/red/chapter11.html + TPolyFillType = (pftEvenOdd, pftNonZero, pftPositive, pftNegative); + + //TJoinType - used by OffsetPolygons() + TJoinType = (jtSquare, jtRound, jtMiter); + + TPolygon = array of TIntPoint; + TPolygons = array of TPolygon; + + TPolyNode = class; + TArrayOfPolyNode = array of TPolyNode; + + TPolyNode = class + private + FPolygon: TPolygon; + FParent : TPolyNode; + FIndex: Integer; + FCount : Integer; + FBuffLen : Integer; + FChilds: TArrayOfPolyNode; + function GetChild(Index: Integer): TPolyNode; + function IsHoleNode: boolean; + procedure AddChild(PolyNode: TPolyNode); + function GetNextSiblingUp: TPolyNode; + public + function GetNext: TPolyNode; + property ChildCount: Integer read FCount; + property Childs[index: Integer]: TPolyNode read GetChild; + property Parent: TPolyNode read FParent; + property IsHole: Boolean read IsHoleNode; + property Contour: TPolygon read FPolygon; + end; + + TPolyTree = class(TPolyNode) //replaces TExPolygons + private + FAllNodes: TArrayOfPolyNode; //container for ALL PolyNodes + function GetTotal: Integer; + public + procedure Clear; + function GetFirst: TPolyNode; + destructor Destroy; override; + property Total: Integer read GetTotal; + end; + + + //definitions below here are used internally ... + TEdgeSide = (esLeft, esRight); + TIntersectProtect = (ipLeft, ipRight); + TIntersectProtects = set of TIntersectProtect; + TDirection = (dRightToLeft, dLeftToRight); + + PEdge = ^TEdge; + TEdge = record + XBot : Int64; //bottom + YBot : Int64; + XCurr: Int64; //current (ie relative to bottom of current scanbeam) + YCurr: Int64; + XTop : Int64; //top + YTop : Int64; + TmpX : Int64; + Dx : Double; //the inverse of slope + DeltaX: Int64; + DeltaY: Int64; + PolyType : TPolyType; + Side : TEdgeSide; + WindDelta: Integer; //1 or -1 depending on winding direction + WindCnt : Integer; + WindCnt2 : Integer; //winding count of the opposite PolyType + OutIdx : Integer; + Next : PEdge; + Prev : PEdge; + NextInLML: PEdge; + PrevInAEL: PEdge; + NextInAEL: PEdge; + PrevInSEL: PEdge; + NextInSEL: PEdge; + end; + + PEdgeArray = ^TEdgeArray; + TEdgeArray = array[0.. MaxInt div sizeof(TEdge) -1] of TEdge; + + PScanbeam = ^TScanbeam; + TScanbeam = record + Y : Int64; + Next: PScanbeam; + end; + + PIntersectNode = ^TIntersectNode; + TIntersectNode = record + Edge1: PEdge; + Edge2: PEdge; + Pt : TIntPoint; + Next : PIntersectNode; + end; + + PLocalMinima = ^TLocalMinima; + TLocalMinima = record + Y : Int64; + LeftBound : PEdge; + RightBound: PEdge; + Next : PLocalMinima; + end; + + POutPt = ^TOutPt; + + POutRec = ^TOutRec; + TOutRec = record + Idx : Integer; + BottomPt : POutPt; + IsHole : Boolean; + //The 'FirstLeft' field points to the OutRec struct containing the + //polygon immediately to the left of the current OutRec's polygon. + //When polygons are contained within other polygons, the polygon + //immediately to the left will either be the outer/owner polygon or a + //sibling contained by the same outer polygon. By storing and later parsing + //this FirstLeft field, it's easy to sort polygons into a tree structure + //that reflects the parent/child relationships of all the polygons. + FirstLeft : POutRec; + Pts : POutPt; + PolyNode : TPolyNode; + end; + TArrayOfOutRec = array of POutRec; + + TOutPt = record + Idx : Integer; + Pt : TIntPoint; + Next : POutPt; + Prev : POutPt; + end; + + PJoinRec = ^TJoinRec; + TJoinRec = record + Pt1a : TIntPoint; + Pt1b : TIntPoint; + Poly1Idx : Integer; + Pt2a : TIntPoint; + Pt2b : TIntPoint; + Poly2Idx : Integer; + end; + + PHorzRec = ^THorzRec; + THorzRec = record + Edge : PEdge; + SavedIdx : Integer; + Next : PHorzRec; + Prev : PHorzRec; + end; + + TClipperBase = class + private + FEdgeList : TList; + FLmList : PLocalMinima; //localMinima list + FCurrLm : PLocalMinima; //current localMinima node + FUse64BitRange : Boolean; //see LoRange and HiRange consts notes below + procedure DisposeLocalMinimaList; + protected + procedure Reset; virtual; + procedure PopLocalMinima; + property CurrentLm: PLocalMinima read FCurrLm; + public + constructor Create; virtual; + destructor Destroy; override; + function AddPolygon(const polygon: TPolygon; PolyType: TPolyType): Boolean; + function AddPolygons(const polygons: TPolygons; PolyType: TPolyType): Boolean; + procedure Clear; virtual; + end; + + TClipper = class(TClipperBase) + private + FPolyOutList : TList; + FJoinList : TList; + FClipType : TClipType; + FScanbeam : PScanbeam; //scanbeam list + FActiveEdges : PEdge; //active Edge list + FSortedEdges : PEdge; //used for temporary sorting + FIntersectNodes : PIntersectNode; + FClipFillType : TPolyFillType; + FSubjFillType : TPolyFillType; + FExecuteLocked : Boolean; + FHorizJoins : PHorzRec; + FReverseOutput : Boolean; + FUsingPolyTree: Boolean; + procedure DisposeScanbeamList; + procedure InsertScanbeam(const Y: Int64); + function PopScanbeam: Int64; + procedure SetWindingCount(Edge: PEdge); + function IsEvenOddFillType(Edge: PEdge): Boolean; + function IsEvenOddAltFillType(Edge: PEdge): Boolean; + procedure AddEdgeToSEL(Edge: PEdge); + procedure CopyAELToSEL; + procedure InsertLocalMinimaIntoAEL(const BotY: Int64); + procedure SwapPositionsInAEL(E1, E2: PEdge); + procedure SwapPositionsInSEL(E1, E2: PEdge); + function IsTopHorz(const XPos: Int64): Boolean; + procedure ProcessHorizontal(HorzEdge: PEdge); + procedure ProcessHorizontals; + procedure AddIntersectNode(E1, E2: PEdge; const Pt: TIntPoint); + function ProcessIntersections(const BotY, TopY: Int64): Boolean; + procedure BuildIntersectList(const BotY, TopY: Int64); + procedure ProcessIntersectList; + procedure DeleteFromAEL(E: PEdge); + procedure DeleteFromSEL(E: PEdge); + procedure IntersectEdges(E1,E2: PEdge; + const Pt: TIntPoint; protects: TIntersectProtects = []); + procedure DoMaxima(E: PEdge; const TopY: Int64); + procedure UpdateEdgeIntoAEL(var E: PEdge); + function FixupIntersections: Boolean; + procedure SwapIntersectNodes(Int1, Int2: PIntersectNode); + procedure ProcessEdgesAtTopOfScanbeam(const TopY: Int64); + function IsContributing(Edge: PEdge): Boolean; + function CreateOutRec: POutRec; + procedure AddOutPt(E: PEdge; const Pt: TIntPoint); + procedure AddLocalMaxPoly(E1, E2: PEdge; const Pt: TIntPoint); + procedure AddLocalMinPoly(E1, E2: PEdge; const Pt: TIntPoint); + procedure AppendPolygon(E1, E2: PEdge); + procedure DisposePolyPts(PP: POutPt); + procedure DisposeAllPolyPts; + procedure DisposeOutRec(Index: Integer); + procedure DisposeIntersectNodes; + function GetResult: TPolygons; + function GetResult2(PolyTree: TPolyTree): Boolean; + procedure FixupOutPolygon(OutRec: POutRec); + procedure SetHoleState(E: PEdge; OutRec: POutRec); + procedure AddJoin(E1, E2: PEdge; + E1OutIdx: Integer = -1; E2OutIdx: Integer = -1); + procedure ClearJoins; + procedure AddHorzJoin(E: PEdge; Idx: Integer); + procedure ClearHorzJoins; + function JoinPoints(JR: PJoinRec; out P1, P2: POutPt): Boolean; + procedure FixupJoinRecs(JR: PJoinRec; Pt: POutPt; StartIdx: Integer); + procedure FixupFirstLefts1(OldOutRec, NewOutRec: POutRec); + procedure FixupFirstLefts2(OldOutRec, NewOutRec: POutRec); + procedure JoinCommonEdges; + procedure FixHoleLinkage(OutRec: POutRec); + protected + procedure Reset; override; + function ExecuteInternal: Boolean; virtual; + public + function Execute(clipType: TClipType; + out solution: TPolygons; + subjFillType: TPolyFillType = pftEvenOdd; + clipFillType: TPolyFillType = pftEvenOdd): Boolean; overload; + function Execute(clipType: TClipType; + var PolyTree: TPolyTree; + subjFillType: TPolyFillType = pftEvenOdd; + clipFillType: TPolyFillType = pftEvenOdd): Boolean; overload; + constructor Create; override; + destructor Destroy; override; + procedure Clear; override; + //ReverseSolution: reverses the default orientation + property ReverseSolution: Boolean read FReverseOutput write FReverseOutput; + end; + +function Orientation(const Pts: TPolygon): Boolean; overload; +function Area(const Pts: TPolygon): Double; +function IntPoint(const X, Y: Int64): TIntPoint; +function ReversePolygon(const Pts: TPolygon): TPolygon; +function ReversePolygons(const Pts: TPolygons): TPolygons; + +//OffsetPolygons precondition: outer polygons MUST be oriented clockwise, +//and inner 'hole' polygons must be oriented counter-clockwise ... +function OffsetPolygons(const Polys: TPolygons; const Delta: Double; + JoinType: TJoinType = jtSquare; MiterLimit: Double = 2; + AutoFix: Boolean = True): TPolygons; + +//SimplifyPolygon converts a self-intersecting polygon into a simple polygon. +function SimplifyPolygon(const poly: TPolygon; FillType: TPolyFillType = pftEvenOdd): TPolygons; +function SimplifyPolygons(const polys: TPolygons; FillType: TPolyFillType = pftEvenOdd): TPolygons; + +//CleanPolygon removes adjacent vertices closer than the specified distance. +function CleanPolygon(Poly: TPolygon; Distance: double = 1.415): TPolygon; +function CleanPolygons(Polys: TPolygons; Distance: double = 1.415): TPolygons; + +function PolyTreeToPolygons(PolyTree: TPolyTree): TPolygons; + +implementation + +type + TDoublePoint = record X, Y: Double; end; + TArrayOfDoublePoint = array of TDoublePoint; + +const + Horizontal: Double = -3.4e+38; + //The Area function places the most limits on coordinate values + //So, to avoid overflow errors, they must not exceed the following values... + LoRange: Int64 = $3FFFFFFF; //1.0e+9 + HiRange: Int64 = $3FFFFFFFFFFFFFFF; //4.6e+18 + //Also, if all coordinates are within +/-LoRange, then calculations will be + //faster. Otherwise using Int128 math will render the library ~10-15% slower. + +resourcestring + rsMissingRightbound = 'InsertLocalMinimaIntoAEL: missing RightBound'; + rsDoMaxima = 'DoMaxima error'; + rsUpdateEdgeIntoAEL = 'UpdateEdgeIntoAEL error'; + rsHorizontal = 'ProcessHorizontal error'; + rsInvalidInt = 'Coordinate exceeds range bounds'; + rsJoinError = 'Join Output polygons error'; + +//------------------------------------------------------------------------------ +// TPolyNode methods ... +//------------------------------------------------------------------------------ + +function TPolyNode.GetChild(Index: Integer): TPolyNode; +begin + if (Index < 0) or (Index >= FCount) then + raise Exception.Create('TPolyNode range error: ' + inttostr(Index)); + Result := FChilds[Index]; +end; +//------------------------------------------------------------------------------ + +procedure TPolyNode.AddChild(PolyNode: TPolyNode); +begin + if FCount = FBuffLen then + begin + Inc(FBuffLen, 16); + SetLength(FChilds, FBuffLen); + end; + PolyNode.FParent := self; + PolyNode.FIndex := FCount; + FChilds[FCount] := PolyNode; + Inc(FCount); +end; +//------------------------------------------------------------------------------ + +function TPolyNode.IsHoleNode: boolean; +var + Node: TPolyNode; +begin + Result := True; + Node := FParent; + while Assigned(Node) do + begin + Result := not Result; + Node := Node.FParent; + end; +end; +//------------------------------------------------------------------------------ + +function TPolyNode.GetNext: TPolyNode; +begin + if FCount > 0 then + Result := FChilds[0] else + Result := GetNextSiblingUp; +end; +//------------------------------------------------------------------------------ + +function TPolyNode.GetNextSiblingUp: TPolyNode; +begin + if not Assigned(FParent) then //protects against TPolyTree.GetNextSiblingUp() + Result := nil + else if FIndex = FParent.FCount -1 then + Result := FParent.GetNextSiblingUp + else + Result := FParent.Childs[FIndex +1]; +end; + +//------------------------------------------------------------------------------ +// TPolyTree methods ... +//------------------------------------------------------------------------------ + +destructor TPolyTree.Destroy; +begin + Clear; + inherited; +end; +//------------------------------------------------------------------------------ + +procedure TPolyTree.Clear; +var + I: Integer; +begin + for I := 0 to high(FAllNodes) do FAllNodes[I].Free; + FAllNodes := nil; + FBuffLen := 16; + SetLength(FChilds, FBuffLen); + FCount := 0; +end; +//------------------------------------------------------------------------------ + +function TPolyTree.GetFirst: TPolyNode; +begin + if FCount > 0 then + Result := FChilds[0] else + Result := nil; +end; +//------------------------------------------------------------------------------ + +function TPolyTree.GetTotal: Integer; +begin + Result := length(FAllNodes); +end; + +//------------------------------------------------------------------------------ +// Int128 Functions ... +//------------------------------------------------------------------------------ + +const + Mask32Bits = $FFFFFFFF; + +type + + //nb: TInt128.Lo is typed Int64 instead of UInt64 to provide Delphi 7 + //compatability. However while UInt64 isn't a recognised type in + //Delphi 7, it can still be used in typecasts. + TInt128 = record + Hi : Int64; + Lo : Int64; + end; + +{$OVERFLOWCHECKS OFF} +procedure Int128Negate(var Val: TInt128); +begin + if Val.Lo = 0 then + begin + Val.Hi := -Val.Hi; + end else + begin + Val.Lo := -Val.Lo; + Val.Hi := not Val.Hi; + end; +end; +//------------------------------------------------------------------------------ + +function Int128(const val: Int64): TInt128; overload; +begin + Result.Lo := val; + if val < 0 then + Result.Hi := -1 else + Result.Hi := 0; +end; +//------------------------------------------------------------------------------ + +function Int128Equal(const Int1, Int2: TInt128): Boolean; +begin + Result := (Int1.Lo = Int2.Lo) and (Int1.Hi = Int2.Hi); +end; +//------------------------------------------------------------------------------ + +function Int128LessThan(const Int1, Int2: TInt128): Boolean; +begin + if (Int1.Hi <> Int2.Hi) then Result := Int1.Hi < Int2.Hi + else Result := UInt64(Int1.Lo) < UInt64(Int2.Lo); +end; +//------------------------------------------------------------------------------ + +function Int128Add(const Int1, Int2: TInt128): TInt128; +begin + Result.Lo := Int1.Lo + Int2.Lo; + Result.Hi := Int1.Hi + Int2.Hi; + if UInt64(Result.Lo) < UInt64(Int1.Lo) then Inc(Result.Hi); +end; +//------------------------------------------------------------------------------ + +function Int128Sub(const Int1, Int2: TInt128): TInt128; +begin + Result.Hi := Int1.Hi - Int2.Hi; + Result.Lo := Int1.Lo - Int2.Lo; + if UInt64(Result.Lo) > UInt64(Int1.Lo) then Dec(Result.Hi); +end; +//------------------------------------------------------------------------------ + +function Int128Mul(Int1, Int2: Int64): TInt128; +var + A, B, C: Int64; + Int1Hi, Int1Lo, Int2Hi, Int2Lo: Int64; + Negate: Boolean; +begin + //save the Result's sign before clearing both sign bits ... + Negate := (Int1 < 0) <> (Int2 < 0); + if Int1 < 0 then Int1 := -Int1; + if Int2 < 0 then Int2 := -Int2; + + Int1Hi := Int1 shr 32; + Int1Lo := Int1 and Mask32Bits; + Int2Hi := Int2 shr 32; + Int2Lo := Int2 and Mask32Bits; + + A := Int1Hi * Int2Hi; + B := Int1Lo * Int2Lo; + //because the high (sign) bits in both int1Hi & int2Hi have been zeroed, + //there's no risk of 64 bit overflow in the following assignment + //(ie: $7FFFFFFF*$FFFFFFFF + $7FFFFFFF*$FFFFFFFF < 64bits) + C := Int1Hi*Int2Lo + Int2Hi*Int1Lo; + //Result = A shl 64 + C shl 32 + B ... + Result.Hi := A + (C shr 32); + A := C shl 32; + + Result.Lo := A + B; + if UInt64(Result.Lo) < UInt64(A) then + Inc(Result.Hi); + + if Negate then Int128Negate(Result); +end; +//------------------------------------------------------------------------------ + +function Int128Div(Dividend, Divisor: TInt128{; out Remainder: TInt128}): TInt128; +var + Cntr: TInt128; + Negate: Boolean; +begin + if (Divisor.Lo = 0) and (Divisor.Hi = 0) then + raise Exception.create('int128Div error: divide by zero'); + + Negate := (Divisor.Hi < 0) <> (Dividend.Hi < 0); + if Dividend.Hi < 0 then Int128Negate(Dividend); + if Divisor.Hi < 0 then Int128Negate(Divisor); + + if Int128LessThan(Divisor, Dividend) then + begin + Result.Hi := 0; + Result.Lo := 0; + Cntr.Lo := 1; + Cntr.Hi := 0; + //while (Dividend >= Divisor) do + while not Int128LessThan(Dividend, Divisor) do + begin + //divisor := divisor shl 1; + Divisor.Hi := Divisor.Hi shl 1; + if Divisor.Lo < 0 then Inc(Divisor.Hi); + Divisor.Lo := Divisor.Lo shl 1; + + //Cntr := Cntr shl 1; + Cntr.Hi := Cntr.Hi shl 1; + if Cntr.Lo < 0 then Inc(Cntr.Hi); + Cntr.Lo := Cntr.Lo shl 1; + end; + //Divisor := Divisor shr 1; + Divisor.Lo := Divisor.Lo shr 1; + if Divisor.Hi and $1 = $1 then + Int64Rec(Divisor.Lo).Hi := Cardinal(Int64Rec(Divisor.Lo).Hi) or $80000000; + Divisor.Hi := Divisor.Hi shr 1; + + //Cntr := Cntr shr 1; + Cntr.Lo := Cntr.Lo shr 1; + if Cntr.Hi and $1 = $1 then + Int64Rec(Cntr.Lo).Hi := Cardinal(Int64Rec(Cntr.Lo).Hi) or $80000000; + Cntr.Hi := Cntr.Hi shr 1; + + //while (Cntr > 0) do + while not ((Cntr.Hi = 0) and (Cntr.Lo = 0)) do + begin + //if ( Dividend >= Divisor) then + if not Int128LessThan(Dividend, Divisor) then + begin + //Dividend := Dividend - Divisor; + Dividend := Int128Sub(Dividend, Divisor); + + //Result := Result or Cntr; + Result.Hi := Result.Hi or Cntr.Hi; + Result.Lo := Result.Lo or Cntr.Lo; + end; + //Divisor := Divisor shr 1; + Divisor.Lo := Divisor.Lo shr 1; + if Divisor.Hi and $1 = $1 then + Int64Rec(Divisor.Lo).Hi := Cardinal(Int64Rec(Divisor.Lo).Hi) or $80000000; + Divisor.Hi := Divisor.Hi shr 1; + + //Cntr := Cntr shr 1; + Cntr.Lo := Cntr.Lo shr 1; + if Cntr.Hi and $1 = $1 then + Int64Rec(Cntr.Lo).Hi := Cardinal(Int64Rec(Cntr.Lo).Hi) or $80000000; + Cntr.Hi := Cntr.Hi shr 1; + end; + if Negate then Int128Negate(Result); + //Remainder := Dividend; + end + else if (Divisor.Hi = Dividend.Hi) and (Divisor.Lo = Dividend.Lo) then + begin + Result := Int128(1); + end else + begin + Result := Int128(0); + end; +end; +//------------------------------------------------------------------------------ + +function Int128AsDouble(val: TInt128): Double; +const + shift64: Double = 18446744073709551616.0; +var + lo: Int64; +begin + if (val.Hi < 0) then + begin + lo := -val.Lo; + if lo = 0 then + Result := val.Hi * shift64 else + Result := -(not val.Hi * shift64 + UInt64(lo)); + end else + Result := val.Hi * shift64 + UInt64(val.Lo); +end; +//------------------------------------------------------------------------------ + +{$OVERFLOWCHECKS ON} + +//------------------------------------------------------------------------------ +// Miscellaneous Functions ... +//------------------------------------------------------------------------------ + +function FullRangeNeeded(const Pts: TPolygon): Boolean; +var + I: Integer; +begin + Result := False; + for I := 0 to high(Pts) do + begin + if (abs(Pts[I].X) > HiRange) or (abs(Pts[I].Y) > HiRange) then + raise exception.Create(rsInvalidInt) + else if (abs(Pts[I].X) > LoRange) or (abs(Pts[I].Y) > LoRange) then + Result := True; + end; +end; +//------------------------------------------------------------------------------ + +function PointCount(Pts: POutPt): Integer; +var + P: POutPt; +begin + Result := 0; + if not Assigned(Pts) then Exit; + P := Pts; + repeat + Inc(Result); + P := P.Next; + until P = Pts; +end; +//------------------------------------------------------------------------------ + +function PointsEqual(const P1, P2: TIntPoint): Boolean; +begin + Result := (P1.X = P2.X) and (P1.Y = P2.Y); +end; +//------------------------------------------------------------------------------ + +function IntPoint(const X, Y: Int64): TIntPoint; +begin + Result.X := X; + Result.Y := Y; +end; +//------------------------------------------------------------------------------ + +function Area(const Pts: TPolygon): Double; overload; +var + I, HighI: Integer; + A: TInt128; + D: Double; +begin + Result := 0; + HighI := high(Pts); + if HighI < 2 then Exit; + if FullRangeNeeded(Pts) then + begin + A := Int128Sub(Int128Mul(Pts[HighI].X, Pts[0].Y), + Int128Mul(Pts[0].X, Pts[HighI].Y)); + for I := 0 to HighI-1 do + A := Int128Add(A, Int128Sub(Int128Mul(Pts[I].X, Pts[I+1].Y), + Int128Mul(Pts[I+1].X, Pts[I].Y))); + Result := Int128AsDouble(A) / 2; + end else + begin + //see http://www.mathopenref.com/coordpolygonarea2.html + D := (Pts[HighI].X + Pts[0].X) * (Pts[0].Y - Pts[HighI].Y); + for I := 1 to HighI do + D := D + (Pts[I-1].X + Pts[I].X) * (Pts[I].Y - Pts[I-1].Y); + Result := D / 2; + end; +end; +//------------------------------------------------------------------------------ + +function Area(OutRec: POutRec; UseFullInt64Range: Boolean): Double; overload; +var + Op: POutPt; + D: Double; + A: TInt128; +begin + Op := OutRec.Pts; + if not Assigned(Op) then + begin + Result := 0; + Exit; + end; + if UseFullInt64Range then + begin + A := Int128(0); + repeat + A := Int128Add(A, + Int128Mul(Op.Pt.X + Op.Prev.Pt.X, Op.Prev.Pt.Y - Op.Pt.Y)); + Op := Op.Next; + until Op = OutRec.Pts; + Result := Int128AsDouble(A) / 2; + end else + begin + D := 0; + repeat + //nb: subtraction reversed since vertices are stored in reverse order ... + D := D + (Op.Pt.X + Op.Prev.Pt.X) * (Op.Prev.Pt.Y - Op.Pt.Y); + Op := Op.Next; + until Op = OutRec.Pts; + Result := D / 2; + end; +end; +//------------------------------------------------------------------------------ + +function Orientation(const Pts: TPolygon): Boolean; overload; +begin + Result := Area(Pts) >= 0; +end; +//------------------------------------------------------------------------------ + +function ReversePolygon(const Pts: TPolygon): TPolygon; +var + I, HighI: Integer; +begin + HighI := high(Pts); + SetLength(Result, HighI +1); + for I := 0 to HighI do + Result[I] := Pts[HighI - I]; +end; +//------------------------------------------------------------------------------ + +function ReversePolygons(const Pts: TPolygons): TPolygons; +var + I, J, highJ: Integer; +begin + I := length(Pts); + SetLength(Result, I); + for I := 0 to I -1 do + begin + highJ := high(Pts[I]); + SetLength(Result[I], highJ+1); + for J := 0 to highJ do + Result[I][J] := Pts[I][highJ - J]; + end; +end; +//------------------------------------------------------------------------------ + +function PointIsVertex(const Pt: TIntPoint; PP: POutPt): Boolean; +var + Pp2: POutPt; +begin + Result := True; + Pp2 := PP; + repeat + if PointsEqual(Pp2.Pt, Pt) then Exit; + Pp2 := Pp2.Next; + until Pp2 = PP; + Result := False; +end; +//------------------------------------------------------------------------------ + +function PointInPolygon(const Pt: TIntPoint; + PP: POutPt; UseFullInt64Range: Boolean): Boolean; +var + Pp2: POutPt; + A, B: TInt128; +begin + Result := False; + Pp2 := PP; + if UseFullInt64Range then + begin + repeat + if (((Pp2.Pt.Y <= Pt.Y) and (Pt.Y < Pp2.Prev.Pt.Y)) or + ((Pp2.Prev.Pt.Y <= Pt.Y) and (Pt.Y < Pp2.Pt.Y))) then + begin + A := Int128(Pt.X - Pp2.Pt.X); + B := Int128Div( Int128Mul(Pp2.Prev.Pt.X - Pp2.Pt.X, + Pt.Y - Pp2.Pt.Y), Int128(Pp2.Prev.Pt.Y - Pp2.Pt.Y) ); + if Int128LessThan(A, B) then Result := not Result; + end; + Pp2 := Pp2.Next; + until Pp2 = PP; + end else + begin + repeat + if ((((Pp2.Pt.Y <= Pt.Y) and (Pt.Y < Pp2.Prev.Pt.Y)) or + ((Pp2.Prev.Pt.Y <= Pt.Y) and (Pt.Y < Pp2.Pt.Y))) and + (Pt.X < (Pp2.Prev.Pt.X - Pp2.Pt.X) * (Pt.Y - Pp2.Pt.Y) / + (Pp2.Prev.Pt.Y - Pp2.Pt.Y) + Pp2.Pt.X)) then Result := not Result; + Pp2 := Pp2.Next; + until Pp2 = PP; + end; +end; +//------------------------------------------------------------------------------ + +function SlopesEqual(E1, E2: PEdge; + UseFullInt64Range: Boolean): Boolean; overload; +begin + if UseFullInt64Range then + Result := Int128Equal(Int128Mul(E1.DeltaY, E2.DeltaX), + Int128Mul(E1.DeltaX, E2.DeltaY)) + else + Result := E1.DeltaY * E2.DeltaX = E1.DeltaX * E2.DeltaY; +end; +//--------------------------------------------------------------------------- + +function SlopesEqual(const Pt1, Pt2, Pt3: TIntPoint; + UseFullInt64Range: Boolean): Boolean; overload; +begin + if UseFullInt64Range then + Result := Int128Equal( + Int128Mul(Pt1.Y-Pt2.Y, Pt2.X-Pt3.X), Int128Mul(Pt1.X-Pt2.X, Pt2.Y-Pt3.Y)) + else + Result := (Pt1.Y-Pt2.Y)*(Pt2.X-Pt3.X) = (Pt1.X-Pt2.X)*(Pt2.Y-Pt3.Y); +end; +//--------------------------------------------------------------------------- + +function SlopesEqual(const Pt1, Pt2, Pt3, Pt4: TIntPoint; + UseFullInt64Range: Boolean): Boolean; overload; +begin + if UseFullInt64Range then + Result := Int128Equal( Int128Mul(Pt1.Y-Pt2.Y, Pt3.X-Pt4.X), + Int128Mul(Pt1.X-Pt2.X, Pt3.Y-Pt4.Y)) + else + Result := (Pt1.Y-Pt2.Y)*(Pt3.X-Pt4.X) = (Pt1.X-Pt2.X)*(Pt3.Y-Pt4.Y); +end; +//--------------------------------------------------------------------------- + +// 0(90º) // +// | // +// +inf (180º) --- o --- -inf (0º) // +function GetDx(const Pt1, Pt2: TIntPoint): Double; +begin + if (Pt1.Y = Pt2.Y) then Result := Horizontal + else Result := (Pt2.X - Pt1.X)/(Pt2.Y - Pt1.Y); +end; +//--------------------------------------------------------------------------- + +procedure SetDx(E: PEdge); +begin + E.DeltaX := (E.XTop - E.XBot); + E.DeltaY := (E.YTop - E.YBot); + if E.DeltaY = 0 then E.Dx := Horizontal + else E.Dx := E.DeltaX/E.DeltaY; +end; +//--------------------------------------------------------------------------- + +procedure SwapSides(Edge1, Edge2: PEdge); +var + Side: TEdgeSide; +begin + Side := Edge1.Side; + Edge1.Side := Edge2.Side; + Edge2.Side := Side; +end; +//------------------------------------------------------------------------------ + +procedure SwapPolyIndexes(Edge1, Edge2: PEdge); +var + OutIdx: Integer; +begin + OutIdx := Edge1.OutIdx; + Edge1.OutIdx := Edge2.OutIdx; + Edge2.OutIdx := OutIdx; +end; +//------------------------------------------------------------------------------ + +function TopX(Edge: PEdge; const currentY: Int64): Int64; overload; +begin + if currentY = Edge.YTop then Result := Edge.XTop + else if Edge.XTop = Edge.XBot then Result := Edge.XBot + else Result := Edge.XBot + round(Edge.Dx*(currentY - Edge.YBot)); +end; +//------------------------------------------------------------------------------ + +function IntersectPoint(Edge1, Edge2: PEdge; + out ip: TIntPoint; UseFullInt64Range: Boolean): Boolean; overload; +var + B1,B2,M: Double; +begin + if SlopesEqual(Edge1, Edge2, UseFullInt64Range) then + begin + Result := False; + Exit; + end; + if Edge1.Dx = 0 then + begin + ip.X := Edge1.XBot; + if Edge2.Dx = Horizontal then + ip.Y := Edge2.YBot + else + begin + with Edge2^ do B2 := YBot - (XBot/Dx); + ip.Y := round(ip.X/Edge2.Dx + B2); + end; + end + else if Edge2.Dx = 0 then + begin + ip.X := Edge2.XBot; + if Edge1.Dx = Horizontal then + ip.Y := Edge1.YBot + else + begin + with Edge1^ do B1 := YBot - (XBot/Dx); + ip.Y := round(ip.X/Edge1.Dx + B1); + end; + end else + begin + with Edge1^ do B1 := XBot - YBot * Dx; + with Edge2^ do B2 := XBot - YBot * Dx; + M := (B2-B1)/(Edge1.Dx - Edge2.Dx); + ip.Y := round(M); + if Abs(Edge1.Dx) < Abs(Edge2.Dx) then + ip.X := round(Edge1.Dx * M + B1) + else + ip.X := round(Edge2.Dx * M + B2); + end; + + //The precondition - E.TmpX > eNext.TmpX - indicates that the two edges do + //intersect below TopY (and hence below the tops of either Edge). However, + //when edges are almost parallel, rounding errors may cause False positives - + //indicating intersections when there really aren't any. Also, floating point + //imprecision can incorrectly place an intersect point beyond/above an Edge. + //Therfore, further validation of the IP is warranted ... + if (ip.Y < Edge1.YTop) or (ip.Y < Edge2.YTop) then + begin + //Find the lower top of the two edges and compare X's at this Y. + //If Edge1's X is greater than Edge2's X then it's fair to assume an + //intersection really has occurred... + if (Edge1.YTop > Edge2.YTop) then + begin + Result := TopX(Edge2, Edge1.YTop) < Edge1.XTop; + ip.X := Edge1.XTop; + ip.Y := Edge1.YTop; + end else + begin + Result := TopX(Edge1, Edge2.YTop) > Edge2.XTop; + ip.X := Edge2.XTop; + ip.Y := Edge2.YTop; + end; + end else + Result := True; +end; +//------------------------------------------------------------------------------ + +procedure ReversePolyPtLinks(PP: POutPt); +var + Pp1,Pp2: POutPt; +begin + if not Assigned(PP) then Exit; + Pp1 := PP; + repeat + Pp2:= Pp1.Next; + Pp1.Next := Pp1.Prev; + Pp1.Prev := Pp2; + Pp1 := Pp2; + until Pp1 = PP; +end; + +//------------------------------------------------------------------------------ +// TClipperBase methods ... +//------------------------------------------------------------------------------ + +constructor TClipperBase.Create; +begin + FEdgeList := TList.Create; + FLmList := nil; + FCurrLm := nil; + FUse64BitRange := False; //ie default is False +end; +//------------------------------------------------------------------------------ + +destructor TClipperBase.Destroy; +begin + Clear; + FEdgeList.Free; + inherited; +end; +//------------------------------------------------------------------------------ + +function TClipperBase.AddPolygon(const polygon: TPolygon; + PolyType: TPolyType): Boolean; + + //---------------------------------------------------------------------- + + procedure InitEdge(E, eNext, ePrev: PEdge; const Pt: TIntPoint); + begin + fillChar(E^, sizeof(TEdge), 0); + E.Next := eNext; + E.Prev := ePrev; + E.XCurr := Pt.X; + E.YCurr := Pt.Y; + if E.YCurr >= E.Next.YCurr then + begin + E.XBot := E.XCurr; + E.YBot := E.YCurr; + E.XTop := E.Next.XCurr; + E.YTop := E.Next.YCurr; + E.WindDelta := 1; + end else + begin + E.XTop := E.XCurr; + E.YTop := E.YCurr; + E.XBot := E.Next.XCurr; + E.YBot := E.Next.YCurr; + E.WindDelta := -1; + end; + SetDx(E); + E.PolyType := PolyType; + E.OutIdx := -1; + end; + //---------------------------------------------------------------------- + + procedure SwapX(E: PEdge); + begin + //swap horizontal edges' top and bottom x's so they follow the natural + //progression of the bounds - ie so their xbots will align with the + //adjoining lower Edge. [Helpful in the ProcessHorizontal() method.] + E.XCurr := E.XTop; + E.XTop := E.XBot; + E.XBot := E.XCurr; + end; + //---------------------------------------------------------------------- + + procedure InsertLocalMinima(lm: PLocalMinima); + var + TmpLm: PLocalMinima; + begin + if not Assigned(fLmList) then + begin + FLmList := lm; + end + else if (lm.Y >= FLmList.Y) then + begin + lm.Next := FLmList; + FLmList := lm; + end else + begin + TmpLm := FLmList; + while Assigned(TmpLm.Next) and (lm.Y < TmpLm.Next.Y) do + TmpLm := TmpLm.Next; + lm.Next := TmpLm.Next; + TmpLm.Next := lm; + end; + end; + //---------------------------------------------------------------------- + + function AddBoundsToLML(E: PEdge): PEdge; + var + NewLm: PLocalMinima; + begin + //Starting at the top of one bound we progress to the bottom where there's + //A local minima. We then go to the top of the Next bound. These two bounds + //form the left and right (or right and left) bounds of the local minima. + E.NextInLML := nil; + E := E.Next; + while True do + begin + if E.Dx = Horizontal then + begin + //nb: proceed through horizontals when approaching from their right, + // but break on horizontal minima if approaching from their left. + // This ensures 'local minima' are always on the left of horizontals. + if (E.Next.YTop < E.YTop) and (E.Next.XBot > E.Prev.XBot) then Break; + if (E.XTop <> E.Prev.XBot) then SwapX(E); + //E.WindDelta := 0; safe option to consider when redesigning + E.NextInLML := E.Prev; + end + else if (E.YBot = E.Prev.YBot) then Break + else E.NextInLML := E.Prev; + E := E.Next; + end; + + //E and E.Prev are now at a local minima ... + new(NewLm); + NewLm.Y := E.Prev.YBot; + NewLm.Next := nil; + if E.Dx = Horizontal then //Horizontal edges never start a left bound + begin + if (E.XBot <> E.Prev.XBot) then SwapX(E); + NewLm.LeftBound := E.Prev; + NewLm.RightBound := E; + end else if (E.Dx < E.Prev.Dx) then + begin + NewLm.LeftBound := E.Prev; + NewLm.RightBound := E; + end else + begin + NewLm.LeftBound := E; + NewLm.RightBound := E.Prev; + end; + NewLm.LeftBound.Side := esLeft; + NewLm.RightBound.Side := esRight; + + InsertLocalMinima(NewLm); + //now process the ascending bound .... + while True do + begin + if (E.Next.YTop = E.YTop) and not (E.Next.Dx = Horizontal) then Break; + E.NextInLML := E.Next; + E := E.Next; + if (E.Dx = Horizontal) and (E.XBot <> E.Prev.XTop) then SwapX(E); + end; + Result := E.Next; + end; + //---------------------------------------------------------------------- + +var + I, J, len: Integer; + Edges: PEdgeArray; + E, EHighest: PEdge; + Pg: TPolygon; + MaxVal: Int64; +begin + {AddPolygon} + Result := False; //ie assume nothing added + len := length(polygon); + if len < 3 then Exit; + SetLength(Pg, len); + Pg[0] := polygon[0]; + J := 0; + //1. check that coordinate values are within the valid range, and + //2. remove duplicate points and co-linear points + if FUse64BitRange then MaxVal := HiRange else MaxVal := LoRange; + for I := 1 to len-1 do + begin + if ((abs(polygon[I].X) > MaxVal) or (abs(polygon[I].Y) > MaxVal)) then + begin + if ((abs(polygon[I].X) > HiRange) or (abs(polygon[I].Y) > HiRange)) then + raise exception.Create(rsInvalidInt); + MaxVal := HiRange; + FUse64BitRange := True; + end; + if PointsEqual(Pg[J], polygon[I]) then Continue + else if (J > 0) and SlopesEqual(Pg[J-1], Pg[J], polygon[I], FUse64BitRange) then + begin + if PointsEqual(Pg[J-1], polygon[I]) then Dec(J); + end else Inc(J); + Pg[J] := polygon[I]; + end; + if (J < 2) then Exit; + + //now remove duplicate points and co-linear edges at the loop around of the + //start and end coordinates ... + len := J+1; + while len > 2 do + begin + //nb: test for point equality before testing slopes ... + if PointsEqual(Pg[J], Pg[0]) then Dec(J) + else if PointsEqual(Pg[0], Pg[1]) or + SlopesEqual(Pg[J], Pg[0], Pg[1], FUse64BitRange) then + begin + Pg[0] := Pg[J]; + Dec(J); + end + else if SlopesEqual(Pg[J-1], Pg[J], Pg[0], FUse64BitRange) then Dec(J) + else if SlopesEqual(Pg[0], Pg[1], Pg[2], FUse64BitRange) then + begin + for I := 2 to J do Pg[I-1] := Pg[I]; + Dec(J); + end + else + Break; + Dec(len); + end; + if len < 3 then Exit; + Result := True; + + GetMem(Edges, sizeof(TEdge)*len); + FEdgeList.Add(Edges); + + //convert vertices to a Double-linked-list of edges and initialize ... + Edges[0].XCurr := Pg[0].X; + Edges[0].YCurr := Pg[0].Y; + InitEdge(@Edges[len-1], @Edges[0], @Edges[len-2], Pg[len-1]); + for I := len-2 downto 1 do + InitEdge(@Edges[I], @Edges[I+1], @Edges[I-1], Pg[I]); + InitEdge(@Edges[0], @Edges[1], @Edges[len-1], Pg[0]); + //reset XCurr & YCurr and find the 'highest' Edge. (nb: since I'm much more + //familiar with positive downwards Y axes, 'highest' here will be the Edge + //with the *smallest* YTop.) + E := @Edges[0]; + EHighest := E; + repeat + E.XCurr := E.XBot; + E.YCurr := E.YBot; + if E.YTop < EHighest.YTop then EHighest := E; + E := E.Next; + until E = @Edges[0]; + + //make sure eHighest is positioned so the following loop works safely ... + if EHighest.WindDelta > 0 then EHighest := EHighest.Next; + if (EHighest.Dx = Horizontal) then EHighest := EHighest.Next; + + //finally insert each local minima ... + E := EHighest; + repeat + E := AddBoundsToLML(E); + until (E = EHighest); +end; +//------------------------------------------------------------------------------ + +function TClipperBase.AddPolygons(const polygons: TPolygons; + PolyType: TPolyType): Boolean; +var + I: Integer; +begin + Result := False; + for I := 0 to high(polygons) do + if AddPolygon(polygons[I], PolyType) then Result := True; +end; +//------------------------------------------------------------------------------ + +procedure TClipperBase.Clear; +var + I: Integer; +begin + DisposeLocalMinimaList; + for I := 0 to FEdgeList.Count -1 do dispose(PEdgeArray(fEdgeList[I])); + FEdgeList.Clear; + FUse64BitRange := False; +end; +//------------------------------------------------------------------------------ + +procedure TClipperBase.Reset; +var + E: PEdge; + Lm: PLocalMinima; +begin + //Reset() allows various clipping operations to be executed + //multiple times on the same polygon sets. + + FCurrLm := FLmList; + //reset all edges ... + Lm := FCurrLm; + while Assigned(Lm) do + begin + E := Lm.LeftBound; + while Assigned(E) do + begin + E.XCurr := E.XBot; + E.YCurr := E.YBot; + E.Side := esLeft; + E.OutIdx := -1; + E := E.NextInLML; + end; + E := Lm.RightBound; + while Assigned(E) do + begin + E.XCurr := E.XBot; + E.YCurr := E.YBot; + E.Side := esRight; + E.OutIdx := -1; + E := E.NextInLML; + end; + Lm := Lm.Next; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipperBase.DisposeLocalMinimaList; +begin + while Assigned(fLmList) do + begin + FCurrLm := FLmList.Next; + Dispose(fLmList); + FLmList := FCurrLm; + end; + FCurrLm := nil; +end; +//------------------------------------------------------------------------------ + +procedure TClipperBase.PopLocalMinima; +begin + if not Assigned(fCurrLM) then Exit; + FCurrLM := FCurrLM.Next; +end; + +//------------------------------------------------------------------------------ +// TClipper methods ... +//------------------------------------------------------------------------------ + +constructor TClipper.Create; +begin + inherited Create; + FJoinList := TList.Create; + FPolyOutList := TList.Create; +end; +//------------------------------------------------------------------------------ + +destructor TClipper.Destroy; +begin + inherited; //this must be first since inherited Destroy calls Clear. + DisposeScanbeamList; + FJoinList.Free; + FPolyOutList.Free; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.Clear; +begin + DisposeAllPolyPts; + inherited; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.DisposeScanbeamList; +var + SB: PScanbeam; +begin + while Assigned(fScanbeam) do + begin + SB := FScanbeam.Next; + Dispose(fScanbeam); + FScanbeam := SB; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.Reset; +var + Lm: PLocalMinima; +begin + inherited Reset; + FScanbeam := nil; + DisposeAllPolyPts; + Lm := FLmList; + while Assigned(Lm) do + begin + InsertScanbeam(Lm.Y); + InsertScanbeam(Lm.LeftBound.YTop); + Lm := Lm.Next; + end; +end; +//------------------------------------------------------------------------------ + +function TClipper.Execute(clipType: TClipType; + out solution: TPolygons; + subjFillType: TPolyFillType = pftEvenOdd; + clipFillType: TPolyFillType = pftEvenOdd): Boolean; +begin + Result := False; + solution := nil; + if FExecuteLocked then Exit; + try + FExecuteLocked := True; + FSubjFillType := subjFillType; + FClipFillType := clipFillType; + FClipType := clipType; + FUsingPolyTree := False; + Result := ExecuteInternal; + if Result then solution := GetResult; + finally + FExecuteLocked := False; + end; +end; +//------------------------------------------------------------------------------ + +function TClipper.Execute(clipType: TClipType; + var PolyTree: TPolyTree; + subjFillType: TPolyFillType = pftEvenOdd; + clipFillType: TPolyFillType = pftEvenOdd): Boolean; +begin + Result := False; + if FExecuteLocked or not Assigned(PolyTree) then Exit; + try + FExecuteLocked := True; + FSubjFillType := subjFillType; + FClipFillType := clipFillType; + FClipType := clipType; + FUsingPolyTree := True; + Result := ExecuteInternal and GetResult2(PolyTree); + finally + FExecuteLocked := False; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.FixHoleLinkage(OutRec: POutRec); +var + orfl: POutRec; +begin + //skip if an outermost polygon or + //already already points to the correct FirstLeft ... + if not Assigned(OutRec.FirstLeft) or + ((OutRec.IsHole <> OutRec.FirstLeft.IsHole) and + Assigned(OutRec.FirstLeft.Pts)) then Exit; + orfl := OutRec.FirstLeft; + while Assigned(orfl) and + ((orfl.IsHole = OutRec.IsHole) or not Assigned(orfl.Pts)) do + orfl := orfl.FirstLeft; + OutRec.FirstLeft := orfl; +end; +//------------------------------------------------------------------------------ + +function TClipper.ExecuteInternal: Boolean; +var + I: Integer; + OutRec: POutRec; + BotY, TopY: Int64; +begin + Result := False; + try try + Reset; + if not Assigned(fScanbeam) then + begin + Result := True; + Exit; + end; + + BotY := PopScanbeam; + repeat + InsertLocalMinimaIntoAEL(BotY); + ClearHorzJoins; + ProcessHorizontals; + TopY := PopScanbeam; + if not ProcessIntersections(BotY, TopY) then Exit; + ProcessEdgesAtTopOfScanbeam(TopY); + BotY := TopY; + until FScanbeam = nil; + + //tidy up output polygons and fix orientations where necessary ... + for I := 0 to FPolyOutList.Count -1 do + begin + OutRec := FPolyOutList[I]; + if not Assigned(OutRec.Pts) then Continue; + FixupOutPolygon(OutRec); + if not Assigned(OutRec.Pts) then Continue; + if (OutRec.IsHole xor FReverseOutput) = (Area(OutRec, FUse64BitRange) > 0) then + ReversePolyPtLinks(OutRec.Pts); + end; + if FJoinList.count > 0 then JoinCommonEdges; + Result := True; + except + Result := False; + end; + finally + ClearJoins; + ClearHorzJoins; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.InsertScanbeam(const Y: Int64); +var + Sb, Sb2: PScanbeam; +begin + new(Sb); + Sb.Y := Y; + if not Assigned(fScanbeam) then + begin + FScanbeam := Sb; + Sb.Next := nil; + end else if Y > FScanbeam.Y then + begin + Sb.Next := FScanbeam; + FScanbeam := Sb; + end else + begin + Sb2 := FScanbeam; + while Assigned(Sb2.Next) and (Y <= Sb2.Next.Y) do Sb2 := Sb2.Next; + if Y <> Sb2.Y then + begin + Sb.Next := Sb2.Next; + Sb2.Next := Sb; + end + else dispose(Sb); //ie ignores duplicates + end; +end; +//------------------------------------------------------------------------------ + +function TClipper.PopScanbeam: Int64; +var + Sb: PScanbeam; +begin + Result := FScanbeam.Y; + Sb := FScanbeam; + FScanbeam := FScanbeam.Next; + dispose(Sb); +end; +//------------------------------------------------------------------------------ + +procedure TClipper.DisposePolyPts(PP: POutPt); +var + TmpPp: POutPt; +begin + PP.Prev.Next := nil; + while Assigned(PP) do + begin + TmpPp := PP; + PP := PP.Next; + dispose(TmpPp); + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.DisposeAllPolyPts; +var + I: Integer; +begin + for I := 0 to FPolyOutList.Count -1 do DisposeOutRec(I); + FPolyOutList.Clear; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.DisposeOutRec(Index: Integer); +var + OutRec: POutRec; +begin + OutRec := FPolyOutList[Index]; + if Assigned(OutRec.Pts) then DisposePolyPts(OutRec.Pts); + Dispose(OutRec); + FPolyOutList[Index] := nil; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.SetWindingCount(Edge: PEdge); +var + E: PEdge; +begin + E := Edge.PrevInAEL; + //find the Edge of the same PolyType that immediately preceeds 'Edge' in AEL + while Assigned(E) and (E.PolyType <> Edge.PolyType) do E := E.PrevInAEL; + if not Assigned(E) then + begin + Edge.WindCnt := Edge.WindDelta; + Edge.WindCnt2 := 0; + E := FActiveEdges; //ie get ready to calc WindCnt2 + end else if IsEvenOddFillType(Edge) then + begin + //even-odd filling ... + Edge.WindCnt := 1; + Edge.WindCnt2 := E.WindCnt2; + E := E.NextInAEL; //ie get ready to calc WindCnt2 + end else + begin + //NonZero, Positive, or Negative filling ... + if E.WindCnt * E.WindDelta < 0 then + begin + if (abs(E.WindCnt) > 1) then + begin + if (E.WindDelta * Edge.WindDelta < 0) then Edge.WindCnt := E.WindCnt + else Edge.WindCnt := E.WindCnt + Edge.WindDelta; + end else + Edge.WindCnt := E.WindCnt + E.WindDelta + Edge.WindDelta; + end else + begin + if (abs(E.WindCnt) > 1) and (E.WindDelta * Edge.WindDelta < 0) then + Edge.WindCnt := E.WindCnt + else if E.WindCnt + Edge.WindDelta = 0 then + Edge.WindCnt := E.WindCnt + else Edge.WindCnt := E.WindCnt + Edge.WindDelta; + end; + Edge.WindCnt2 := E.WindCnt2; + E := E.NextInAEL; //ie get ready to calc WindCnt2 + end; + + //update WindCnt2 ... + if IsEvenOddAltFillType(Edge) then + begin + //even-odd filling ... + while (E <> Edge) do + begin + if Edge.WindCnt2 = 0 then Edge.WindCnt2 := 1 else Edge.WindCnt2 := 0; + E := E.NextInAEL; + end; + end else + begin + //NonZero, Positive, or Negative filling ... + while (E <> Edge) do + begin + Inc(Edge.WindCnt2, E.WindDelta); + E := E.NextInAEL; + end; + end; +end; +//------------------------------------------------------------------------------ + +function TClipper.IsEvenOddFillType(Edge: PEdge): Boolean; +begin + if Edge.PolyType = ptSubject then + Result := FSubjFillType = pftEvenOdd else + Result := FClipFillType = pftEvenOdd; +end; +//------------------------------------------------------------------------------ + +function TClipper.IsEvenOddAltFillType(Edge: PEdge): Boolean; +begin + if Edge.PolyType = ptSubject then + Result := FClipFillType = pftEvenOdd else + Result := FSubjFillType = pftEvenOdd; +end; +//------------------------------------------------------------------------------ + +function TClipper.IsContributing(Edge: PEdge): Boolean; +var + Pft, Pft2: TPolyFillType; +begin + if Edge.PolyType = ptSubject then + begin + Pft := FSubjFillType; + Pft2 := FClipFillType; + end else + begin + Pft := FClipFillType; + Pft2 := FSubjFillType + end; + case Pft of + pftEvenOdd, pftNonZero: Result := abs(Edge.WindCnt) = 1; + pftPositive: Result := (Edge.WindCnt = 1); + else Result := (Edge.WindCnt = -1); + end; + if not Result then Exit; + + case FClipType of + ctIntersection: + case Pft2 of + pftEvenOdd, pftNonZero: Result := (Edge.WindCnt2 <> 0); + pftPositive: Result := (Edge.WindCnt2 > 0); + pftNegative: Result := (Edge.WindCnt2 < 0); + end; + ctUnion: + case Pft2 of + pftEvenOdd, pftNonZero: Result := (Edge.WindCnt2 = 0); + pftPositive: Result := (Edge.WindCnt2 <= 0); + pftNegative: Result := (Edge.WindCnt2 >= 0); + end; + ctDifference: + if Edge.PolyType = ptSubject then + case Pft2 of + pftEvenOdd, pftNonZero: Result := (Edge.WindCnt2 = 0); + pftPositive: Result := (Edge.WindCnt2 <= 0); + pftNegative: Result := (Edge.WindCnt2 >= 0); + end + else + case Pft2 of + pftEvenOdd, pftNonZero: Result := (Edge.WindCnt2 <> 0); + pftPositive: Result := (Edge.WindCnt2 > 0); + pftNegative: Result := (Edge.WindCnt2 < 0); + end; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.AddLocalMinPoly(E1, E2: PEdge; const Pt: TIntPoint); +var + E, prevE: PEdge; +begin + if (E2.Dx = Horizontal) or (E1.Dx > E2.Dx) then + begin + AddOutPt(E1, Pt); + E2.OutIdx := E1.OutIdx; + E1.Side := esLeft; + E2.Side := esRight; + E := E1; + if E.PrevInAEL = E2 then + prevE := E2.PrevInAEL + else + prevE := E.PrevInAEL; + end else + begin + AddOutPt(E2, Pt); + E1.OutIdx := E2.OutIdx; + E1.Side := esRight; + E2.Side := esLeft; + E := E2; + if E.PrevInAEL = E1 then + prevE := E1.PrevInAEL + else + prevE := E.PrevInAEL; + end; + + if Assigned(prevE) and (prevE.OutIdx >= 0) and + (TopX(prevE, Pt.Y) = TopX(E, Pt.Y)) and + SlopesEqual(E, prevE, FUse64BitRange) then + AddJoin(E, prevE); +end; +//------------------------------------------------------------------------------ + +procedure TClipper.AddLocalMaxPoly(E1, E2: PEdge; const Pt: TIntPoint); +begin + AddOutPt(E1, Pt); + if (E1.OutIdx = E2.OutIdx) then + begin + E1.OutIdx := -1; + E2.OutIdx := -1; + end + else if E1.OutIdx < E2.OutIdx then + AppendPolygon(E1, E2) + else + AppendPolygon(E2, E1); +end; +//------------------------------------------------------------------------------ + +procedure TClipper.AddEdgeToSEL(Edge: PEdge); +begin + //SEL pointers in PEdge are reused to build a list of horizontal edges. + //However, we don't need to worry about order with horizontal Edge processing. + if not Assigned(fSortedEdges) then + begin + FSortedEdges := Edge; + Edge.PrevInSEL := nil; + Edge.NextInSEL := nil; + end else + begin + Edge.NextInSEL := FSortedEdges; + Edge.PrevInSEL := nil; + FSortedEdges.PrevInSEL := Edge; + FSortedEdges := Edge; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.CopyAELToSEL; +var + E: PEdge; +begin + E := FActiveEdges; + FSortedEdges := E; + while Assigned(E) do + begin + E.PrevInSEL := E.PrevInAEL; + E.NextInSEL := E.NextInAEL; + E := E.NextInAEL; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.AddJoin(E1, E2: PEdge; + E1OutIdx: Integer = -1; E2OutIdx: Integer = -1); +var + Jr: PJoinRec; +begin + new(Jr); + if E1OutIdx >= 0 then + Jr.Poly1Idx := E1OutIdx else + Jr.Poly1Idx := E1.OutIdx; + with E1^ do + begin + Jr.Pt1a := IntPoint(XCurr, YCurr); + Jr.Pt1b := IntPoint(XTop, YTop); + end; + if E2OutIdx >= 0 then + Jr.Poly2Idx := E2OutIdx else + Jr.Poly2Idx := E2.OutIdx; + with E2^ do + begin + Jr.Pt2a := IntPoint(XCurr, YCurr); + Jr.Pt2b := IntPoint(XTop, YTop); + end; + FJoinList.add(Jr); +end; +//------------------------------------------------------------------------------ + +procedure TClipper.ClearJoins; +var + I: Integer; +begin + for I := 0 to FJoinList.count -1 do + Dispose(PJoinRec(fJoinList[I])); + FJoinList.Clear; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.AddHorzJoin(E: PEdge; Idx: Integer); +var + Hr: PHorzRec; +begin + new(Hr); + Hr.Edge := E; + Hr.SavedIdx := Idx; + if FHorizJoins = nil then + begin + FHorizJoins := Hr; + Hr.Next := Hr; + Hr.Prev := Hr; + end else + begin + Hr.Next := FHorizJoins; + Hr.Prev := FHorizJoins.Prev; + FHorizJoins.Prev.Next := Hr; + FHorizJoins.Prev := Hr; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.ClearHorzJoins; +var + M, M2: PHorzRec; +begin + if not Assigned(fHorizJoins) then Exit; + M := FHorizJoins; + M.Prev.Next := nil; + while Assigned(M) do + begin + M2 := M.Next; + dispose(M); + M := M2; + end; + FHorizJoins := nil; +end; +//------------------------------------------------------------------------------ + +procedure SwapPoints(var Pt1, Pt2: TIntPoint); +var + Tmp: TIntPoint; +begin + Tmp := Pt1; + Pt1 := Pt2; + Pt2 := Tmp; +end; +//------------------------------------------------------------------------------ + +function GetOverlapSegment(Pt1a, Pt1b, Pt2a, Pt2b: TIntPoint; + out Pt1, Pt2: TIntPoint): Boolean; +begin + //precondition: segments are colinear + if abs(Pt1a.X - Pt1b.X) > abs(Pt1a.Y - Pt1b.Y) then + begin + if Pt1a.X > Pt1b.X then SwapPoints(Pt1a, Pt1b); + if Pt2a.X > Pt2b.X then SwapPoints(Pt2a, Pt2b); + if (Pt1a.X > Pt2a.X) then Pt1 := Pt1a else Pt1 := Pt2a; + if (Pt1b.X < Pt2b.X) then Pt2 := Pt1b else Pt2 := Pt2b; + Result := Pt1.X < Pt2.X; + end else + begin + if Pt1a.Y < Pt1b.Y then SwapPoints(Pt1a, Pt1b); + if Pt2a.Y < Pt2b.Y then SwapPoints(Pt2a, Pt2b); + if (Pt1a.Y < Pt2a.Y) then Pt1 := Pt1a else Pt1 := Pt2a; + if (Pt1b.Y > Pt2b.Y) then Pt2 := Pt1b else Pt2 := Pt2b; + Result := Pt1.Y > Pt2.Y; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.InsertLocalMinimaIntoAEL(const BotY: Int64); + + function E2InsertsBeforeE1(E1,E2: PEdge): Boolean; + begin + if E2.XCurr = E1.XCurr then + Result := E2.Dx > E1.Dx else + Result := E2.XCurr < E1.XCurr; + end; + //---------------------------------------------------------------------- + + procedure InsertEdgeIntoAEL(Edge: PEdge); + var + E: PEdge; + begin + Edge.PrevInAEL := nil; + Edge.NextInAEL := nil; + if not Assigned(fActiveEdges) then + begin + FActiveEdges := Edge; + end else if E2InsertsBeforeE1(fActiveEdges, Edge) then + begin + Edge.NextInAEL := FActiveEdges; + FActiveEdges.PrevInAEL := Edge; + FActiveEdges := Edge; + end else + begin + E := FActiveEdges; + while Assigned(E.NextInAEL) and + not E2InsertsBeforeE1(E.NextInAEL, Edge) do + E := E.NextInAEL; + Edge.NextInAEL := E.NextInAEL; + if Assigned(E.NextInAEL) then E.NextInAEL.PrevInAEL := Edge; + Edge.PrevInAEL := E; + E.NextInAEL := Edge; + end; + end; + //---------------------------------------------------------------------- + +var + E: PEdge; + Pt, Pt2: TIntPoint; + Lb, Rb: PEdge; + Hj: PHorzRec; +begin + while Assigned(CurrentLm) and (CurrentLm.Y = BotY) do + begin + Lb := CurrentLm.LeftBound; + Rb := CurrentLm.RightBound; + + InsertEdgeIntoAEL(Lb); + InsertScanbeam(Lb.YTop); + InsertEdgeIntoAEL(Rb); + + //set Edge winding states ... + if IsEvenOddFillType(Lb) then + begin + Lb.WindDelta := 1; + Rb.WindDelta := 1; + end else + begin + Rb.WindDelta := -Lb.WindDelta + end; + SetWindingCount(Lb); + Rb.WindCnt := Lb.WindCnt; + Rb.WindCnt2 := Lb.WindCnt2; + + if Rb.Dx = Horizontal then + begin + AddEdgeToSEL(Rb); + InsertScanbeam(Rb.NextInLML.YTop); + end else + InsertScanbeam(Rb.YTop); + + if IsContributing(Lb) then + AddLocalMinPoly(Lb, Rb, IntPoint(Lb.XCurr, CurrentLm.Y)); + + //if output polygons share an Edge with rb, they'll need joining later ... + if (Rb.OutIdx >= 0) and (Rb.Dx = Horizontal) and Assigned(fHorizJoins) then + begin + Hj := FHorizJoins; + repeat + //if horizontals rb & hj.Edge overlap, flag for joining later ... + if GetOverlapSegment(IntPoint(Hj.Edge.XBot, Hj.Edge.YBot), + IntPoint(Hj.Edge.XTop, Hj.Edge.YTop), IntPoint(Rb.XBot, Rb.YBot), + IntPoint(Rb.XTop, Rb.YTop), Pt, Pt2) then + AddJoin(Hj.Edge, Rb, Hj.SavedIdx); + Hj := Hj.Next; + until Hj = FHorizJoins; + end; + + if (Lb.NextInAEL <> Rb) then + begin + if (Rb.OutIdx >= 0) and (Rb.PrevInAEL.OutIdx >= 0) and + SlopesEqual(Rb.PrevInAEL, Rb, FUse64BitRange) then + AddJoin(Rb, Rb.PrevInAEL); + + E := Lb.NextInAEL; + Pt := IntPoint(Lb.XCurr,Lb.YCurr); + while E <> Rb do + begin + if not Assigned(E) then raise exception.Create(rsMissingRightbound); + //nb: For calculating winding counts etc, IntersectEdges() assumes + //that param1 will be to the right of param2 ABOVE the intersection ... + IntersectEdges(Rb, E, Pt); + E := E.NextInAEL; + end; + end; + PopLocalMinima; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.DeleteFromAEL(E: PEdge); +var + AelPrev, AelNext: PEdge; +begin + AelPrev := E.PrevInAEL; + AelNext := E.NextInAEL; + if not Assigned(AelPrev) and not Assigned(AelNext) and + (E <> FActiveEdges) then Exit; //already deleted + if Assigned(AelPrev) then AelPrev.NextInAEL := AelNext + else FActiveEdges := AelNext; + if Assigned(AelNext) then AelNext.PrevInAEL := AelPrev; + E.NextInAEL := nil; + E.PrevInAEL := nil; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.DeleteFromSEL(E: PEdge); +var + SelPrev, SelNext: PEdge; +begin + SelPrev := E.PrevInSEL; + SelNext := E.NextInSEL; + if not Assigned(SelPrev) and not Assigned(SelNext) and + (E <> FSortedEdges) then Exit; //already deleted + if Assigned(SelPrev) then SelPrev.NextInSEL := SelNext + else FSortedEdges := SelNext; + if Assigned(SelNext) then SelNext.PrevInSEL := SelPrev; + E.NextInSEL := nil; + E.PrevInSEL := nil; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.IntersectEdges(E1,E2: PEdge; + const Pt: TIntPoint; protects: TIntersectProtects = []); + + procedure DoEdge1; + begin + AddOutPt(E1, Pt); + SwapSides(E1, E2); + SwapPolyIndexes(E1, E2); + end; + //---------------------------------------------------------------------- + + procedure DoEdge2; + begin + AddOutPt(E2, Pt); + SwapSides(E1, E2); + SwapPolyIndexes(E1, E2); + end; + //---------------------------------------------------------------------- + + procedure DoBothEdges; + begin + AddOutPt(E1, Pt); + AddOutPt(E2, Pt); + SwapSides(E1, E2); + SwapPolyIndexes(E1, E2); + end; + //---------------------------------------------------------------------- + +var + E1stops, E2stops: Boolean; + E1Contributing, E2contributing: Boolean; + E1FillType, E2FillType, E1FillType2, E2FillType2: TPolyFillType; + E1Wc, E2Wc, E1Wc2, E2Wc2: Integer; +begin + {IntersectEdges} + + //E1 will be to the left of E2 BELOW the intersection. Therefore E1 is before + //E2 in AEL except when E1 is being inserted at the intersection point ... + + E1stops := not (ipLeft in protects) and not Assigned(E1.NextInLML) and + (E1.XTop = Pt.x) and (E1.YTop = Pt.Y); + E2stops := not (ipRight in protects) and not Assigned(E2.NextInLML) and + (E2.XTop = Pt.x) and (E2.YTop = Pt.Y); + E1Contributing := (E1.OutIdx >= 0); + E2contributing := (E2.OutIdx >= 0); + + //update winding counts... + //assumes that E1 will be to the right of E2 ABOVE the intersection + if E1.PolyType = E2.PolyType then + begin + if IsEvenOddFillType(E1) then + begin + E1Wc := E1.WindCnt; + E1.WindCnt := E2.WindCnt; + E2.WindCnt := E1Wc; + end else + begin + if E1.WindCnt + E2.WindDelta = 0 then + E1.WindCnt := -E1.WindCnt else + Inc(E1.WindCnt, E2.WindDelta); + if E2.WindCnt - E1.WindDelta = 0 then + E2.WindCnt := -E2.WindCnt else + Dec(E2.WindCnt, E1.WindDelta); + end; + end else + begin + if not IsEvenOddFillType(E2) then Inc(E1.WindCnt2, E2.WindDelta) + else if E1.WindCnt2 = 0 then E1.WindCnt2 := 1 + else E1.WindCnt2 := 0; + if not IsEvenOddFillType(E1) then Dec(E2.WindCnt2, E1.WindDelta) + else if E2.WindCnt2 = 0 then E2.WindCnt2 := 1 + else E2.WindCnt2 := 0; + end; + + if E1.PolyType = ptSubject then + begin + E1FillType := FSubjFillType; + E1FillType2 := FClipFillType; + end else + begin + E1FillType := FClipFillType; + E1FillType2 := FSubjFillType; + end; + if E2.PolyType = ptSubject then + begin + E2FillType := FSubjFillType; + E2FillType2 := FClipFillType; + end else + begin + E2FillType := FClipFillType; + E2FillType2 := FSubjFillType; + end; + + case E1FillType of + pftPositive: E1Wc := E1.WindCnt; + pftNegative : E1Wc := -E1.WindCnt; + else E1Wc := abs(E1.WindCnt); + end; + case E2FillType of + pftPositive: E2Wc := E2.WindCnt; + pftNegative : E2Wc := -E2.WindCnt; + else E2Wc := abs(E2.WindCnt); + end; + + if E1Contributing and E2contributing then + begin + if E1stops or E2stops or not (E1Wc in [0,1]) or not (E2Wc in [0,1]) or + ((E1.PolyType <> E2.PolyType) and (fClipType <> ctXor)) then + AddLocalMaxPoly(E1, E2, Pt) else + DoBothEdges; + end else if E1Contributing then + begin + if ((E2Wc = 0) or (E2Wc = 1)) and + ((fClipType <> ctIntersection) or (E2.PolyType = ptSubject) or + (E2.WindCnt2 <> 0)) then DoEdge1; + end + else if E2contributing then + begin + if ((E1Wc = 0) or (E1Wc = 1)) and + ((fClipType <> ctIntersection) or (E1.PolyType = ptSubject) or + (E1.WindCnt2 <> 0)) then DoEdge2; + end + else if ((E1Wc = 0) or (E1Wc = 1)) and ((E2Wc = 0) or (E2Wc = 1)) and + not E1stops and not E2stops then + begin + //neither Edge is currently contributing ... + + case E1FillType2 of + pftPositive: E1Wc2 := E1.WindCnt2; + pftNegative : E1Wc2 := -E1.WindCnt2; + else E1Wc2 := abs(E1.WindCnt2); + end; + case E2FillType2 of + pftPositive: E2Wc2 := E2.WindCnt2; + pftNegative : E2Wc2 := -E2.WindCnt2; + else E2Wc2 := abs(E2.WindCnt2); + end; + + if (E1.PolyType <> E2.PolyType) then + AddLocalMinPoly(E1, E2, Pt) + else if (E1Wc = 1) and (E2Wc = 1) then + case FClipType of + ctIntersection: + if (E1Wc2 > 0) and (E2Wc2 > 0) then + AddLocalMinPoly(E1, E2, Pt); + ctUnion: + if (E1Wc2 <= 0) and (E2Wc2 <= 0) then + AddLocalMinPoly(E1, E2, Pt); + ctDifference: + if ((E1.PolyType = ptClip) and (E1Wc2 > 0) and (E2Wc2 > 0)) or + ((E1.PolyType = ptSubject) and (E1Wc2 <= 0) and (E2Wc2 <= 0)) then + AddLocalMinPoly(E1, E2, Pt); + ctXor: + AddLocalMinPoly(E1, E2, Pt); + end + else + swapsides(E1,E2); + end; + + if (E1stops <> E2stops) and + ((E1stops and (E1.OutIdx >= 0)) or (E2stops and (E2.OutIdx >= 0))) then + begin + swapsides(E1,E2); + SwapPolyIndexes(E1, E2); + end; + + //finally, delete any non-contributing maxima edges ... + if E1stops then deleteFromAEL(E1); + if E2stops then deleteFromAEL(E2); +end; +//------------------------------------------------------------------------------ + +function FirstParamIsBottomPt(btmPt1, btmPt2: POutPt): Boolean; +var + Dx1n, Dx1p, Dx2n, Dx2p: Double; + P: POutPt; +begin + //Precondition: bottom-points share the same vertex. + //Use inverse slopes of adjacent edges (ie dx/dy) to determine the outer + //polygon and hence the 'real' bottompoint. + //nb: Slope is vertical when dx == 0. If the greater abs(dx) of param1 + //is greater than or equal both abs(dx) in param2 then param1 is outer. + P := btmPt1.Prev; + while PointsEqual(P.Pt, btmPt1.Pt) and (P <> btmPt1) do P := P.Prev; + Dx1p := abs(GetDx(btmPt1.Pt, P.Pt)); + P := btmPt1.Next; + while PointsEqual(P.Pt, btmPt1.Pt) and (P <> btmPt1) do P := P.Next; + Dx1n := abs(GetDx(btmPt1.Pt, P.Pt)); + + P := btmPt2.Prev; + while PointsEqual(P.Pt, btmPt2.Pt) and (P <> btmPt2) do P := P.Prev; + Dx2p := abs(GetDx(btmPt2.Pt, P.Pt)); + P := btmPt2.Next; + while PointsEqual(P.Pt, btmPt2.Pt) and (P <> btmPt2) do P := P.Next; + Dx2n := abs(GetDx(btmPt2.Pt, P.Pt)); + Result := ((Dx1p >= Dx2p) and (Dx1p >= Dx2n)) or + ((Dx1n >= Dx2p) and (Dx1n >= Dx2n)); +end; +//------------------------------------------------------------------------------ + +function GetBottomPt(PP: POutPt): POutPt; +var + P, Dups: POutPt; +begin + Dups := nil; + P := PP.Next; + while P <> PP do + begin + if P.Pt.Y > PP.Pt.Y then + begin + PP := P; + Dups := nil; + end + else if (P.Pt.Y = PP.Pt.Y) and (P.Pt.X <= PP.Pt.X) then + begin + if (P.Pt.X < PP.Pt.X) then + begin + Dups := nil; + PP := P; + end else + begin + if (P.Next <> PP) and (P.Prev <> PP) then Dups := P; + end; + end; + P := P.Next; + end; + if Assigned(Dups) then + begin + //there appears to be at least 2 vertices at BottomPt so ... + while Dups <> P do + begin + if not FirstParamIsBottomPt(P, Dups) then PP := Dups; + Dups := Dups.Next; + while not PointsEqual(Dups.Pt, PP.Pt) do Dups := Dups.Next; + end; + end; + Result := PP; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.SetHoleState(E: PEdge; OutRec: POutRec); +var + E2: PEdge; + IsHole: Boolean; +begin + IsHole := False; + E2 := E.PrevInAEL; + while Assigned(E2) do + begin + if (E2.OutIdx >= 0) then + begin + IsHole := not IsHole; + if not Assigned(OutRec.FirstLeft) then + OutRec.FirstLeft := POutRec(fPolyOutList[E2.OutIdx]); + end; + E2 := E2.PrevInAEL; + end; + if IsHole then + OutRec.IsHole := True; +end; +//------------------------------------------------------------------------------ + +function GetLowermostRec(OutRec1, OutRec2: POutRec): POutRec; +var + OutPt1, OutPt2: POutPt; +begin + OutPt1 := OutRec1.BottomPt; + OutPt2 := OutRec2.BottomPt; + if (OutPt1.Pt.Y > OutPt2.Pt.Y) then Result := OutRec1 + else if (OutPt1.Pt.Y < OutPt2.Pt.Y) then Result := OutRec2 + else if (OutPt1.Pt.X < OutPt2.Pt.X) then Result := OutRec1 + else if (OutPt1.Pt.X > OutPt2.Pt.X) then Result := OutRec2 + else if (OutPt1.Next = OutPt1) then Result := OutRec2 + else if (OutPt2.Next = OutPt2) then Result := OutRec1 + else if FirstParamIsBottomPt(OutPt1, OutPt2) then Result := OutRec1 + else Result := OutRec2; +end; +//------------------------------------------------------------------------------ + +function Param1RightOfParam2(OutRec1, OutRec2: POutRec): Boolean; +begin + Result := True; + repeat + OutRec1 := OutRec1.FirstLeft; + if OutRec1 = OutRec2 then Exit; + until not Assigned(OutRec1); + Result := False; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.AppendPolygon(E1, E2: PEdge); +var + HoleStateRec, OutRec1, OutRec2: POutRec; + P1_lft, P1_rt, P2_lft, P2_rt: POutPt; + NewSide: TEdgeSide; + I, OKIdx, ObsoleteIdx: Integer; + E: PEdge; + Jr: PJoinRec; + H: PHorzRec; +begin + OutRec1 := FPolyOutList[E1.OutIdx]; + OutRec2 := FPolyOutList[E2.OutIdx]; + + //First work out which polygon fragment has the correct hole state. + //Since we're working from the bottom upward and left to right, the left most + //and lowermost polygon is outermost and must have the correct hole state ... + if Param1RightOfParam2(OutRec1, OutRec2) then HoleStateRec := OutRec2 + else if Param1RightOfParam2(OutRec2, OutRec1) then HoleStateRec := OutRec1 + else HoleStateRec := GetLowermostRec(OutRec1, OutRec2); + + //get the start and ends of both output polygons ... + P1_lft := OutRec1.Pts; + P2_lft := OutRec2.Pts; + P1_rt := P1_lft.Prev; + P2_rt := P2_lft.Prev; + + //join E2 poly onto E1 poly and delete pointers to E2 ... + if E1.Side = esLeft then + begin + if E2.Side = esLeft then + begin + //z y x a b c + ReversePolyPtLinks(P2_lft); + P2_lft.Next := P1_lft; + P1_lft.Prev := P2_lft; + P1_rt.Next := P2_rt; + P2_rt.Prev := P1_rt; + OutRec1.Pts := P2_rt; + end else + begin + //x y z a b c + P2_rt.Next := P1_lft; + P1_lft.Prev := P2_rt; + P2_lft.Prev := P1_rt; + P1_rt.Next := P2_lft; + OutRec1.Pts := P2_lft; + end; + NewSide := esLeft; + end else + begin + if E2.Side = esRight then + begin + //a b c z y x + ReversePolyPtLinks(P2_lft); + P1_rt.Next := P2_rt; + P2_rt.Prev := P1_rt; + P2_lft.Next := P1_lft; + P1_lft.Prev := P2_lft; + end else + begin + //a b c x y z + P1_rt.Next := P2_lft; + P2_lft.Prev := P1_rt; + P1_lft.Prev := P2_rt; + P2_rt.Next := P1_lft; + end; + NewSide := esRight; + end; + + if HoleStateRec = OutRec2 then + begin + OutRec1.BottomPt := OutRec2.BottomPt; + OutRec1.BottomPt.Idx := OutRec1.Idx; + if OutRec2.FirstLeft <> OutRec1 then + OutRec1.FirstLeft := OutRec2.FirstLeft; + OutRec1.IsHole := OutRec2.IsHole; + end; + + OutRec2.Pts := nil; + OutRec2.BottomPt := nil; + OutRec2.FirstLeft := OutRec1; + + OKIdx := OutRec1.Idx; + ObsoleteIdx := OutRec2.Idx; + + E1.OutIdx := -1; //nb: safe because we only get here via AddLocalMaxPoly + E2.OutIdx := -1; + + E := FActiveEdges; + while Assigned(E) do + begin + if (E.OutIdx = ObsoleteIdx) then + begin + E.OutIdx := OKIdx; + E.Side := NewSide; + Break; + end; + E := E.NextInAEL; + end; + + for I := 0 to FJoinList.count -1 do + begin + Jr := FJoinList[I]; + if Jr.Poly1Idx = ObsoleteIdx then Jr.Poly1Idx := OKIdx; + if Jr.Poly2Idx = ObsoleteIdx then Jr.Poly2Idx := OKIdx; + end; + if Assigned(fHorizJoins) then + begin + H := FHorizJoins; + repeat + if H.SavedIdx = ObsoleteIdx then H.SavedIdx := OKIdx; + H := H.Next; + until H = FHorizJoins; + end; +end; +//------------------------------------------------------------------------------ + +function TClipper.CreateOutRec: POutRec; +begin + new(Result); + Result.IsHole := False; + Result.FirstLeft := nil; + Result.Pts := nil; + Result.BottomPt := nil; + Result.PolyNode := nil; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.AddOutPt(E: PEdge; const Pt: TIntPoint); +var + OutRec: POutRec; + Op, op2: POutPt; + ToFront: Boolean; +begin + ToFront := E.Side = esLeft; + if E.OutIdx < 0 then + begin + OutRec := CreateOutRec; + OutRec.Idx := FPolyOutList.Add(OutRec); + E.OutIdx := OutRec.Idx; + new(Op); + OutRec.Pts := Op; + OutRec.BottomPt := Op; + + Op.Pt := Pt; + Op.Next := Op; + Op.Prev := Op; + Op.Idx := OutRec.Idx; + SetHoleState(E, OutRec); + end else + begin + OutRec := FPolyOutList[E.OutIdx]; + Op := OutRec.Pts; + if (ToFront and PointsEqual(Pt, Op.Pt)) or + (not ToFront and PointsEqual(Pt, Op.Prev.Pt)) then Exit; + new(op2); + op2.Pt := Pt; + op2.Idx := OutRec.Idx; + if (op2.Pt.Y = OutRec.BottomPt.Pt.Y) and + (op2.Pt.X < OutRec.BottomPt.Pt.X) then + OutRec.BottomPt := op2; + op2.Next := Op; + op2.Prev := Op.Prev; + Op.Prev.Next := op2; + Op.Prev := op2; + if ToFront then OutRec.Pts := op2; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.ProcessHorizontals; +var + E: PEdge; +begin + while Assigned(fSortedEdges) do + begin + E := FSortedEdges; + DeleteFromSEL(E); + ProcessHorizontal(E); + end; +end; +//------------------------------------------------------------------------------ + +function TClipper.IsTopHorz(const XPos: Int64): Boolean; +var + E: PEdge; +begin + Result := False; + E := FSortedEdges; + while Assigned(E) do + begin + if (XPos >= min(E.XCurr,E.XTop)) and (XPos <= max(E.XCurr,E.XTop)) then Exit; + E := E.NextInSEL; + end; + Result := True; +end; +//------------------------------------------------------------------------------ + +function IsMinima(E: PEdge): Boolean; +begin + Result := Assigned(E) and (E.Prev.NextInLML <> E) and (E.Next.NextInLML <> E); +end; +//------------------------------------------------------------------------------ + +function IsMaxima(E: PEdge; const Y: Int64): Boolean; +begin + Result := Assigned(E) and (E.YTop = Y) and not Assigned(E.NextInLML); +end; +//------------------------------------------------------------------------------ + +function IsIntermediate(E: PEdge; const Y: Int64): Boolean; +begin + Result := (E.YTop = Y) and Assigned(E.NextInLML); +end; +//------------------------------------------------------------------------------ + +function GetMaximaPair(E: PEdge): PEdge; +begin + Result := E.Next; + if not IsMaxima(Result, E.YTop) or (Result.XTop <> E.XTop) then + Result := E.Prev; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.SwapPositionsInAEL(E1, E2: PEdge); +var + Prev,Next: PEdge; +begin + if E1.NextInAEL = E2 then + begin + Next := E2.NextInAEL; + if Assigned(Next) then Next.PrevInAEL := E1; + Prev := E1.PrevInAEL; + if Assigned(Prev) then Prev.NextInAEL := E2; + E2.PrevInAEL := Prev; + E2.NextInAEL := E1; + E1.PrevInAEL := E2; + E1.NextInAEL := Next; + end + else if E2.NextInAEL = E1 then + begin + Next := E1.NextInAEL; + if Assigned(Next) then Next.PrevInAEL := E2; + Prev := E2.PrevInAEL; + if Assigned(Prev) then Prev.NextInAEL := E1; + E1.PrevInAEL := Prev; + E1.NextInAEL := E2; + E2.PrevInAEL := E1; + E2.NextInAEL := Next; + end else + begin + Next := E1.NextInAEL; + Prev := E1.PrevInAEL; + E1.NextInAEL := E2.NextInAEL; + if Assigned(E1.NextInAEL) then E1.NextInAEL.PrevInAEL := E1; + E1.PrevInAEL := E2.PrevInAEL; + if Assigned(E1.PrevInAEL) then E1.PrevInAEL.NextInAEL := E1; + E2.NextInAEL := Next; + if Assigned(E2.NextInAEL) then E2.NextInAEL.PrevInAEL := E2; + E2.PrevInAEL := Prev; + if Assigned(E2.PrevInAEL) then E2.PrevInAEL.NextInAEL := E2; + end; + if not Assigned(E1.PrevInAEL) then FActiveEdges := E1 + else if not Assigned(E2.PrevInAEL) then FActiveEdges := E2; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.SwapPositionsInSEL(E1, E2: PEdge); +var + Prev,Next: PEdge; +begin + if E1.NextInSEL = E2 then + begin + Next := E2.NextInSEL; + if Assigned(Next) then Next.PrevInSEL := E1; + Prev := E1.PrevInSEL; + if Assigned(Prev) then Prev.NextInSEL := E2; + E2.PrevInSEL := Prev; + E2.NextInSEL := E1; + E1.PrevInSEL := E2; + E1.NextInSEL := Next; + end + else if E2.NextInSEL = E1 then + begin + Next := E1.NextInSEL; + if Assigned(Next) then Next.PrevInSEL := E2; + Prev := E2.PrevInSEL; + if Assigned(Prev) then Prev.NextInSEL := E1; + E1.PrevInSEL := Prev; + E1.NextInSEL := E2; + E2.PrevInSEL := E1; + E2.NextInSEL := Next; + end else + begin + Next := E1.NextInSEL; + Prev := E1.PrevInSEL; + E1.NextInSEL := E2.NextInSEL; + if Assigned(E1.NextInSEL) then E1.NextInSEL.PrevInSEL := E1; + E1.PrevInSEL := E2.PrevInSEL; + if Assigned(E1.PrevInSEL) then E1.PrevInSEL.NextInSEL := E1; + E2.NextInSEL := Next; + if Assigned(E2.NextInSEL) then E2.NextInSEL.PrevInSEL := E2; + E2.PrevInSEL := Prev; + if Assigned(E2.PrevInSEL) then E2.PrevInSEL.NextInSEL := E2; + end; + if not Assigned(E1.PrevInSEL) then FSortedEdges := E1 + else if not Assigned(E2.PrevInSEL) then FSortedEdges := E2; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.ProcessHorizontal(HorzEdge: PEdge); + + function GetNextInAEL(E: PEdge; Direction: TDirection): PEdge; + begin + if Direction = dLeftToRight then + Result := E.NextInAEL else + Result := E.PrevInAEL; + end; + //------------------------------------------------------------------------ + +var + E, eNext, eMaxPair: PEdge; + HorzLeft, HorzRight: Int64; + Direction: TDirection; +const + ProtectLeft: array[Boolean] of TIntersectProtects = ([ipRight], [ipLeft,ipRight]); + ProtectRight: array[Boolean] of TIntersectProtects = ([ipLeft], [ipLeft,ipRight]); +begin +(******************************************************************************* +* Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or * +* bottom of a scanbeam) are processed as if layered. The order in which HEs * +* are processed doesn't matter. HEs intersect with other HE xbots only [#], * +* and with other non-horizontal edges [*]. Once these intersections are * +* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * +* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * +*******************************************************************************) + +(******************************************************************************* +* \ nb: HE processing order doesn't matter / / * +* \ / / * +* { -------- \ ------------------- / \ - (3) o==========%==========o - } * +* { o==========o (2) / \ . . } * +* { . / \ . . } * +* { ---- o===============#========*========*=====#==========o (1) ------- } * +* / \ / \ / * +*******************************************************************************) + + if HorzEdge.XCurr < HorzEdge.XTop then + begin + HorzLeft := HorzEdge.XCurr; + HorzRight := HorzEdge.XTop; + Direction := dLeftToRight; + end else + begin + HorzLeft := HorzEdge.XTop; + HorzRight := HorzEdge.XCurr; + Direction := dRightToLeft; + end; + + if Assigned(HorzEdge.NextInLML) then + eMaxPair := nil else + eMaxPair := GetMaximaPair(HorzEdge); + + E := GetNextInAEL(HorzEdge, Direction); + while Assigned(E) do + begin + eNext := GetNextInAEL(E, Direction); + if Assigned(eMaxPair) or + ((Direction = dLeftToRight) and (E.XCurr <= HorzRight)) or + ((Direction = dRightToLeft) and (E.XCurr >= HorzLeft)) then + begin + //ok, so far it looks like we're still in range of the horizontal Edge + + if (E.XCurr = HorzEdge.XTop) and not Assigned(eMaxPair) then + begin + if SlopesEqual(E, HorzEdge.NextInLML, FUse64BitRange) then + begin + //if output polygons share an Edge, they'll need joining later ... + if (HorzEdge.OutIdx >= 0) and (E.OutIdx >= 0) then + AddJoin(HorzEdge.NextInLML, E, HorzEdge.OutIdx); + Break; //we've reached the end of the horizontal line + end + else if (E.Dx < HorzEdge.NextInLML.Dx) then + //we really have got to the end of the intermediate horz Edge so quit. + //nb: More -ve slopes follow more +ve slopes ABOVE the horizontal. + Break; + end; + + if (E = eMaxPair) then + begin + //HorzEdge is evidently a maxima horizontal and we've arrived at its end. + if Direction = dLeftToRight then + IntersectEdges(HorzEdge, E, IntPoint(E.XCurr, HorzEdge.YCurr)) else + IntersectEdges(E, HorzEdge, IntPoint(E.XCurr, HorzEdge.YCurr)); + + if (eMaxPair.OutIdx >= 0) then raise exception.Create(rsHorizontal); + Exit; + end + else if (E.Dx = Horizontal) and not IsMinima(E) and not (E.XCurr > E.XTop) then + begin + //An overlapping horizontal Edge. Overlapping horizontal edges are + //processed as if layered with the current horizontal Edge (horizEdge) + //being infinitesimally lower that the Next (E). Therfore, we + //intersect with E only if E.XCurr is within the bounds of HorzEdge ... + if Direction = dLeftToRight then + IntersectEdges(HorzEdge, E, IntPoint(E.XCurr, HorzEdge.YCurr), + ProtectRight[not IsTopHorz(E.XCurr)]) + else + IntersectEdges(E, HorzEdge, IntPoint(E.XCurr, HorzEdge.YCurr), + ProtectLeft[not IsTopHorz(E.XCurr)]); + end + else if (Direction = dLeftToRight) then + IntersectEdges(HorzEdge, E, IntPoint(E.XCurr, HorzEdge.YCurr), + ProtectRight[not IsTopHorz(E.XCurr)]) + else + IntersectEdges(E, HorzEdge, IntPoint(E.XCurr, HorzEdge.YCurr), + ProtectLeft[not IsTopHorz(E.XCurr)]); + SwapPositionsInAEL(HorzEdge, E); + end + else if ((Direction = dLeftToRight) and + (E.XCurr > HorzRight) and Assigned(fSortedEdges)) or + ((Direction = dRightToLeft) and + (E.XCurr < HorzLeft) and Assigned(fSortedEdges)) then + Break; + E := eNext; + end; + + if Assigned(HorzEdge.NextInLML) then + begin + if (HorzEdge.OutIdx >= 0) then + AddOutPt(HorzEdge, IntPoint(HorzEdge.XTop, HorzEdge.YTop)); + UpdateEdgeIntoAEL(HorzEdge); + end else + begin + if HorzEdge.OutIdx >= 0 then + IntersectEdges(HorzEdge, eMaxPair, + IntPoint(HorzEdge.XTop, HorzEdge.YCurr), [ipLeft,ipRight]); + + if eMaxPair.OutIdx >= 0 then raise exception.Create(rsHorizontal); + DeleteFromAEL(eMaxPair); + DeleteFromAEL(HorzEdge); + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.UpdateEdgeIntoAEL(var E: PEdge); +var + AelPrev, AelNext: PEdge; +begin + if not Assigned(E.NextInLML) then raise exception.Create(rsUpdateEdgeIntoAEL); + AelPrev := E.PrevInAEL; + AelNext := E.NextInAEL; + E.NextInLML.OutIdx := E.OutIdx; + if Assigned(AelPrev) then + AelPrev.NextInAEL := E.NextInLML else + FActiveEdges := E.NextInLML; + if Assigned(AelNext) then + AelNext.PrevInAEL := E.NextInLML; + E.NextInLML.Side := E.Side; + E.NextInLML.WindDelta := E.WindDelta; + E.NextInLML.WindCnt := E.WindCnt; + E.NextInLML.WindCnt2 := E.WindCnt2; + E := E.NextInLML; + E.PrevInAEL := AelPrev; + E.NextInAEL := AelNext; + if E.Dx <> Horizontal then + InsertScanbeam(E.YTop); +end; +//------------------------------------------------------------------------------ + +function TClipper.ProcessIntersections(const BotY, TopY: Int64): Boolean; +begin + Result := True; + try + BuildIntersectList(BotY, TopY); + if FIntersectNodes = nil then Exit; + if FixupIntersections then ProcessIntersectList + else Result := False; + finally + //if there's been an error, clean up the mess ... + DisposeIntersectNodes; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.DisposeIntersectNodes; +var + N: PIntersectNode; +begin + while Assigned(fIntersectNodes) do + begin + N := FIntersectNodes.Next; + dispose(fIntersectNodes); + FIntersectNodes := N; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.BuildIntersectList(const BotY, TopY: Int64); +var + E, eNext: PEdge; + Pt: TIntPoint; + IsModified: Boolean; +begin + if not Assigned(fActiveEdges) then Exit; + + //prepare for sorting ... + E := FActiveEdges; + FSortedEdges := E; + while Assigned(E) do + begin + E.PrevInSEL := E.PrevInAEL; + E.NextInSEL := E.NextInAEL; + E.TmpX := TopX(E, TopY); + E := E.NextInAEL; + end; + + try + //bubblesort ... + IsModified := True; + while IsModified and Assigned(fSortedEdges) do + begin + IsModified := False; + E := FSortedEdges; + while Assigned(E.NextInSEL) do + begin + eNext := E.NextInSEL; + if (E.TmpX > eNext.TmpX) and + IntersectPoint(E, eNext, Pt, FUse64BitRange) then + begin + if Pt.Y > BotY then + begin + Pt.Y := BotY; + Pt.X := TopX(E, Pt.Y); + end; + AddIntersectNode(E, eNext, Pt); + SwapPositionsInSEL(E, eNext); + IsModified := True; + end else + E := eNext; + end; + if Assigned(E.PrevInSEL) then E.PrevInSEL.NextInSEL := nil else Break; + end; + finally + FSortedEdges := nil; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.AddIntersectNode(E1, E2: PEdge; const Pt: TIntPoint); + + function ProcessParam1BeforeParam2(node1, node2: PIntersectNode): Boolean; + begin + if node1.Pt.Y = node2.Pt.Y then + begin + if (node1.Edge1 = node2.Edge1) or (node1.Edge2 = node2.Edge1) then + begin + Result := node2.Pt.X > node1.Pt.X; + if node2.Edge1.Dx > 0 then Result := not Result; + end + else if (node1.Edge1 = node2.Edge2) or (node1.Edge2 = node2.Edge2) then + begin + Result := node2.Pt.X > node1.Pt.X; + if node2.Edge2.Dx > 0 then Result := not Result; + end else + Result := node2.Pt.X > node1.Pt.X; + end + else Result := node1.Pt.Y > node2.Pt.Y; + end; + //---------------------------------------------------------------------------- + +var + Node, NewNode: PIntersectNode; +begin + new(NewNode); + NewNode.Edge1 := E1; + NewNode.Edge2 := E2; + NewNode.Pt := Pt; + NewNode.Next := nil; + if not Assigned(fIntersectNodes) then + FIntersectNodes := NewNode + else if ProcessParam1BeforeParam2(NewNode, FIntersectNodes) then + begin + NewNode.Next := FIntersectNodes; + FIntersectNodes := NewNode; + end else + begin + Node := FIntersectNodes; + while Assigned(Node.Next) and + ProcessParam1BeforeParam2(Node.Next, NewNode) do + Node := Node.Next; + NewNode.Next := Node.Next; + Node.Next := NewNode; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.ProcessIntersectList; +var + Node: PIntersectNode; +begin + while Assigned(fIntersectNodes) do + begin + Node := FIntersectNodes.Next; + with FIntersectNodes^ do + begin + IntersectEdges(Edge1, Edge2, Pt, [ipLeft,ipRight]); + SwapPositionsInAEL(Edge1, Edge2); + end; + dispose(fIntersectNodes); + FIntersectNodes := Node; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.DoMaxima(E: PEdge; const TopY: Int64); +var + ENext, EMaxPair: PEdge; + X: Int64; +begin + EMaxPair := GetMaximaPair(E); + X := E.XTop; + ENext := E.NextInAEL; + while ENext <> EMaxPair do + begin + if not Assigned(ENext) then raise exception.Create(rsDoMaxima); + IntersectEdges(E, ENext, IntPoint(X, TopY), [ipLeft, ipRight]); + SwapPositionsInAEL(E, ENext); + ENext := ENext.NextInAEL; + end; + if (E.OutIdx < 0) and (EMaxPair.OutIdx < 0) then + begin + DeleteFromAEL(E); + DeleteFromAEL(EMaxPair); + end + else if (E.OutIdx >= 0) and (EMaxPair.OutIdx >= 0) then + begin + IntersectEdges(E, EMaxPair, IntPoint(X, TopY)); + end + else raise exception.Create(rsDoMaxima); +end; +//------------------------------------------------------------------------------ + +procedure TClipper.ProcessEdgesAtTopOfScanbeam(const TopY: Int64); +var + E, ePrev, eNext: PEdge; + Hj: PHorzRec; + Pt, Pt2: TIntPoint; +begin +(******************************************************************************* +* Notes: Processing edges at scanline intersections (ie at the top or bottom * +* of a scanbeam) needs to be done in multiple stages and in the correct order. * +* Firstly, edges forming a 'maxima' need to be processed and then removed. * +* Next, 'intermediate' and 'maxima' horizontal edges are processed. Then edges * +* that intersect exactly at the top of the scanbeam are processed [%]. * +* Finally, new minima are added and any intersects they create are processed. * +*******************************************************************************) + +(******************************************************************************* +* \ / / \ / * +* \ Horizontal minima / / \ / * +* { -- o======================#====o -------- . ------------------- } * +* { Horizontal maxima . % scanline intersect } * +* { -- o=======================#===================#========o ---------- } * +* | / / \ \ * +* + maxima intersect / / \ \ * +* /|\ / / \ \ * +* / | \ / / \ \ * +*******************************************************************************) + + E := FActiveEdges; + while Assigned(E) do + begin + //1. process maxima, treating them as if they're 'bent' horizontal edges, + // but exclude maxima with Horizontal edges. nb: E can't be a Horizontal. + if IsMaxima(E, TopY) and (GetMaximaPair(E).Dx <> Horizontal) then + begin + //'E' might be removed from AEL, as may any following edges so ... + ePrev := E.PrevInAEL; + DoMaxima(E, TopY); + if not Assigned(ePrev) then + E := FActiveEdges else + E := ePrev.NextInAEL; + end else + begin + //2. promote horizontal edges, otherwise update XCurr and YCurr ... + if IsIntermediate(E, TopY) and (E.NextInLML.Dx = Horizontal) then + begin + if (E.OutIdx >= 0) then + begin + AddOutPt(E, IntPoint(E.XTop, E.YTop)); + + Hj := FHorizJoins; + if Assigned(Hj) then + repeat + if GetOverlapSegment(IntPoint(Hj.Edge.XBot, Hj.Edge.YBot), + IntPoint(Hj.Edge.XTop, Hj.Edge.YTop), + IntPoint(E.NextInLML.XBot, E.NextInLML.YBot), + IntPoint(E.NextInLML.XTop, E.NextInLML.YTop), Pt, Pt2) then + AddJoin(Hj.Edge, E.NextInLML, Hj.SavedIdx, E.OutIdx); + Hj := Hj.Next; + until Hj = FHorizJoins; + + AddHorzJoin(E.NextInLML, E.OutIdx); + end; + UpdateEdgeIntoAEL(E); + AddEdgeToSEL(E); + end else + begin + //this just simplifies horizontal processing ... + E.XCurr := TopX(E, TopY); + E.YCurr := TopY; + end; + E := E.NextInAEL; + end; + end; + + //3. Process horizontals at the top of the scanbeam ... + ProcessHorizontals; + + //4. Promote intermediate vertices ... + E := FActiveEdges; + while Assigned(E) do + begin + if IsIntermediate(E, TopY) then + begin + if (E.OutIdx >= 0) then AddOutPt(E, IntPoint(E.XTop, E.YTop)); + UpdateEdgeIntoAEL(E); + + //if output polygons share an Edge, they'll need joining later ... + ePrev := E.PrevInAEL; + eNext := E.NextInAEL; + if Assigned(ePrev) and (ePrev.XCurr = E.XBot) and + (ePrev.YCurr = E.YBot) and (E.OutIdx >= 0) and + (ePrev.OutIdx >= 0) and (ePrev.YCurr > ePrev.YTop) and + SlopesEqual(E, ePrev, FUse64BitRange) then + begin + AddOutPt(ePrev, IntPoint(E.XBot, E.YBot)); + AddJoin(E, ePrev); + end + else if Assigned(eNext) and (eNext.XCurr = E.XBot) and + (eNext.YCurr = E.YBot) and (E.OutIdx >= 0) and + (eNext.OutIdx >= 0) and (eNext.YCurr > eNext.YTop) and + SlopesEqual(E, eNext, FUse64BitRange) then + begin + AddOutPt(eNext, IntPoint(E.XBot, E.YBot)); + AddJoin(E, eNext); + end; + end; + E := E.NextInAEL; + end; +end; +//------------------------------------------------------------------------------ + +function TClipper.GetResult: TPolygons; +var + I, J, K, Cnt: Integer; + OutRec: POutRec; + Op: POutPt; +begin + J := 0; + SetLength(Result, FPolyOutList.Count); + for I := 0 to FPolyOutList.Count -1 do + if Assigned(fPolyOutList[I]) then + begin + OutRec := FPolyOutList[I]; + Cnt := PointCount(OutRec.Pts); + if (Cnt < 3) then Continue; + SetLength(Result[J], Cnt); + Op := OutRec.Pts; + for K := 0 to Cnt -1 do + begin + Result[J][K].X := Op.Pt.X; + Result[J][K].Y := Op.Pt.Y; + Op := Op.Prev; + end; + Inc(J); + end; + SetLength(Result, J); +end; +//------------------------------------------------------------------------------ + +function TClipper.GetResult2(PolyTree: TPolyTree): Boolean; +var + I, J, Cnt, CntAll: Integer; + Op: POutPt; + OutRec: POutRec; + PolyNode: TPolyNode; +begin + try + PolyTree.Clear; + SetLength(PolyTree.FAllNodes, FPolyOutList.Count); + + //add PolyTree ... + CntAll := 0; + for I := 0 to FPolyOutList.Count -1 do + begin + OutRec := fPolyOutList[I]; + Cnt := PointCount(OutRec.Pts); + if Cnt < 3 then Continue; + FixHoleLinkage(OutRec); + + PolyNode := TPolyNode.Create; + PolyTree.FAllNodes[CntAll] := PolyNode; + OutRec.PolyNode := PolyNode; + Inc(CntAll); + SetLength(PolyNode.FPolygon, Cnt); + Op := OutRec.Pts; + for J := 0 to Cnt -1 do + begin + PolyNode.FPolygon[J].X := Op.Pt.X; + PolyNode.FPolygon[J].Y := Op.Pt.Y; + Op := Op.Prev; + end; + end; + + //fix Poly links ... + SetLength(PolyTree.FAllNodes, CntAll); + SetLength(PolyTree.FChilds, CntAll); + for I := 0 to FPolyOutList.Count -1 do + begin + OutRec := fPolyOutList[I]; + if Assigned(OutRec.PolyNode) then + if Assigned(OutRec.FirstLeft) then + OutRec.FirstLeft.PolyNode.AddChild(OutRec.PolyNode) else + PolyTree.AddChild(OutRec.PolyNode); + end; + SetLength(PolyTree.FChilds, PolyTree.FCount); + Result := True; + except + Result := False; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.FixupOutPolygon(OutRec: POutRec); +var + PP, Tmp, LastOK: POutPt; +begin + //FixupOutPolygon() - removes duplicate points and simplifies consecutive + //parallel edges by removing the middle vertex. + LastOK := nil; + OutRec.Pts := OutRec.BottomPt; + PP := OutRec.Pts; + while True do + begin + if (PP.Prev = PP) or (PP.Next = PP.Prev) then + begin + DisposePolyPts(PP); + OutRec.Pts := nil; + OutRec.BottomPt := nil; + Exit; + end; + + //test for duplicate points and for colinear edges ... + if PointsEqual(PP.Pt, PP.Next.Pt) or + SlopesEqual(PP.Prev.Pt, PP.Pt, PP.Next.Pt, FUse64BitRange) then + begin + //OK, we need to delete a point ... + LastOK := nil; + Tmp := PP; + if PP = OutRec.BottomPt then + OutRec.BottomPt := nil; //flags need for updating + PP.Prev.Next := PP.Next; + PP.Next.Prev := PP.Prev; + PP := PP.Prev; + dispose(Tmp); + end + else if PP = LastOK then Break + else + begin + if not Assigned(LastOK) then LastOK := PP; + PP := PP.Next; + end; + end; + if not Assigned(OutRec.BottomPt) then + begin + OutRec.BottomPt := GetBottomPt(PP); + OutRec.BottomPt.Idx := OutRec.Idx; + OutRec.Pts := OutRec.BottomPt; + end; +end; +//------------------------------------------------------------------------------ + +function TClipper.FixupIntersections: Boolean; +var + E1, E2: PEdge; + Int1, Int2: PIntersectNode; +begin + Result := not Assigned(fIntersectNodes.Next); + if Result then Exit; + //logic: only swap (intersect) adjacent edges ... + try + CopyAELToSEL; + Int1 := FIntersectNodes; + Int2 := FIntersectNodes.Next; + while Assigned(Int2) do + begin + E1 := Int1.Edge1; + if (E1.PrevInSEL = Int1.Edge2) then E2 := E1.PrevInSEL + else if (E1.NextInSEL = Int1.Edge2) then E2 := E1.NextInSEL + else + begin + //The current intersection is out of order, so try and swap it with + //A subsequent intersection ... + while Assigned(Int2) do + begin + if (Int2.Edge1.NextInSEL = Int2.Edge2) or + (Int2.Edge1.PrevInSEL = Int2.Edge2) then Break + else Int2 := Int2.Next; + end; + if not Assigned(Int2) then Exit; //oops!!! + //found an intersect node that can be swapped ... + SwapIntersectNodes(Int1, Int2); + E1 := Int1.Edge1; + E2 := Int1.Edge2; + end; + SwapPositionsInSEL(E1, E2); + Int1 := Int1.Next; + Int2 := Int1.Next; + end; + + //finally, check the last intersection too ... + Result := (Int1.Edge1.PrevInSEL = Int1.Edge2) or + (Int1.Edge1.NextInSEL = Int1.Edge2); + finally + FSortedEdges := nil; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.SwapIntersectNodes(Int1, Int2: PIntersectNode); +var + E1,E2: PEdge; + P: TIntPoint; +begin + with Int1^ do + begin + E1 := Edge1; + Edge1 := Int2.Edge1; + E2 := Edge2; + Edge2 := Int2.Edge2; + P := Pt; + Pt := Int2.Pt; + end; + with Int2^ do + begin + Edge1 := E1; + Edge2 := E2; + Pt := P; + end; +end; +//------------------------------------------------------------------------------ + +function FindSegment(var PP: POutPt; var Pt1, Pt2: TIntPoint): Boolean; +var + Pp2: POutPt; + Pt1a, Pt2a: TIntPoint; +begin + if not Assigned(PP) then begin Result := False; Exit; end; + Result := True; + Pt1a := Pt1; Pt2a := Pt2; + Pp2 := PP; + repeat + //test for co-linearity before testing for overlap ... + if SlopesEqual(Pt1a, Pt2a, PP.Pt, PP.Prev.Pt, True) and + SlopesEqual(Pt1a, Pt2a, PP.Pt, True) and + GetOverlapSegment(Pt1a, Pt2a, PP.Pt, PP.Prev.Pt, Pt1, Pt2) then Exit; + PP := PP.Next; + until PP = Pp2; + Result := False; +end; +//------------------------------------------------------------------------------ + +function Pt3IsBetweenPt1AndPt2(const Pt1, Pt2, Pt3: TIntPoint): Boolean; +begin + if PointsEqual(Pt1, Pt3) or PointsEqual(Pt2, Pt3) then Result := True + else if (Pt1.X <> Pt2.X) then Result := (Pt1.X < Pt3.X) = (Pt3.X < Pt2.X) + else Result := (Pt1.Y < Pt3.Y) = (Pt3.Y < Pt2.Y); +end; +//------------------------------------------------------------------------------ + +function InsertPolyPtBetween(p1, P2: POutPt; const Pt: TIntPoint): POutPt; +begin + if (p1 = P2) then raise exception.Create(rsJoinError); + + new(Result); + Result.Pt := Pt; + Result.Idx := p1.Idx; + if P2 = p1.Next then + begin + p1.Next := Result; + P2.Prev := Result; + Result.Next := P2; + Result.Prev := p1; + end else + begin + P2.Next := Result; + p1.Prev := Result; + Result.Next := p1; + Result.Prev := P2; + end; +end; +//------------------------------------------------------------------------------ + +function TClipper.JoinPoints(JR: PJoinRec; out P1, P2: POutPt): Boolean; +var + OutRec1, OutRec2: POutRec; + Prev, p3, p4, Pp1a, Pp2a: POutPt; + Pt1, Pt2, Pt3, Pt4: TIntPoint; +begin + Result := False; + OutRec1 := FPolyOutList[Jr.Poly1Idx]; + OutRec2 := FPolyOutList[Jr.Poly2Idx]; + if not Assigned(OutRec1) then Exit; + if not Assigned(OutRec2) then Exit; + + Pp1a := OutRec1.Pts; + Pp2a := OutRec2.Pts; + Pt1 := Jr.Pt2a; Pt2 := Jr.Pt2b; + Pt3 := Jr.Pt1a; Pt4 := Jr.Pt1b; + if not FindSegment(Pp1a, Pt1, Pt2) then Exit; + if (OutRec1 = OutRec2) then + begin + //we're searching the same polygon for overlapping segments so + //segment 2 mustn't be the same as segment 1 ... + Pp2a := Pp1a.Next; + if not FindSegment(Pp2a, Pt3, Pt4) or (Pp2a = Pp1a) then Exit; + end else + if not FindSegment(Pp2a, Pt3, Pt4) then Exit; + if not GetOverlapSegment(Pt1, Pt2, Pt3, Pt4, Pt1, Pt2) then Exit; + + Prev := Pp1a.Prev; + if PointsEqual(Pp1a.Pt, Pt1) then P1 := Pp1a + else if PointsEqual(Prev.Pt, Pt1) then P1 := Prev + else P1 := InsertPolyPtBetween(Pp1a, Prev, Pt1); + + if PointsEqual(Pp1a.Pt, Pt2) then P2 := Pp1a + else if PointsEqual(Prev.Pt, Pt2) then P2 := Prev + else if (P1 = Pp1a) or (P1 = Prev) then + P2 := InsertPolyPtBetween(Pp1a, Prev, Pt2) + else if Pt3IsBetweenPt1AndPt2(Pp1a.Pt, P1.Pt, Pt2) then + P2 := InsertPolyPtBetween(Pp1a, P1, Pt2) + else + P2 := InsertPolyPtBetween(P1, Prev, Pt2); + + Prev := Pp2a.Prev; + if PointsEqual(Pp2a.Pt, Pt1) then p3 := Pp2a + else if PointsEqual(Prev.Pt, Pt1) then p3 := Prev + else p3 := InsertPolyPtBetween(Pp2a, Prev, Pt1); + + if PointsEqual(Pp2a.Pt, Pt2) then p4 := Pp2a + else if PointsEqual(Prev.Pt, Pt2) then p4 := Prev + else if (p3 = Pp2a) or (p3 = Prev) then + p4 := InsertPolyPtBetween(Pp2a, Prev, Pt2) + else if Pt3IsBetweenPt1AndPt2(Pp2a.Pt, p3.Pt, Pt2) then + p4 := InsertPolyPtBetween(Pp2a, p3, Pt2) + else + p4 := InsertPolyPtBetween(p3, Prev, Pt2); + + if (P1.Next = P2) and (p3.Prev = p4) then + begin + P1.Next := p3; + p3.Prev := P1; + P2.Prev := p4; + p4.Next := P2; + Result := True; + end + else if (P1.Prev = P2) and (p3.Next = p4) then + begin + P1.Prev := p3; + p3.Next := P1; + P2.Next := p4; + p4.Prev := P2; + Result := True; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.FixupJoinRecs(JR: PJoinRec; Pt: POutPt; StartIdx: Integer); +var + JR2: PJoinRec; +begin + for StartIdx := StartIdx to FJoinList.count -1 do + begin + Jr2 := FJoinList[StartIdx]; + if (Jr2.Poly1Idx = Jr.Poly1Idx) and PointIsVertex(Jr2.Pt1a, Pt) then + Jr2.Poly1Idx := Jr.Poly2Idx; + if (Jr2.Poly2Idx = Jr.Poly1Idx) and PointIsVertex(Jr2.Pt2a, Pt) then + Jr2.Poly2Idx := Jr.Poly2Idx; + end; +end; +//------------------------------------------------------------------------------ + +function Poly2ContainsPoly1(OutPt1, OutPt2: POutPt; + UseFullInt64Range: Boolean): Boolean; +var + OutPt: POutPt; +begin + OutPt := OutPt1; + repeat + if not PointIsVertex(OutPt.Pt, OutPt2) then Break; + OutPt := OutPt.Next; + until OutPt = OutPt1; + //sometimes points can be touching the other polygon so + //to be totally confident OutPt1 is inside OutPt2 repeat ... + repeat + Result := PointInPolygon(OutPt.Pt, OutPt2, UseFullInt64Range); + OutPt := OutPt.Next; + until not Result or (OutPt = OutPt1); +end; +//------------------------------------------------------------------------------ + +procedure TClipper.FixupFirstLefts1(OldOutRec, NewOutRec: POutRec); +var + I: Integer; + OutRec: POutRec; +begin + for I := 0 to FPolyOutList.Count -1 do + begin + OutRec := fPolyOutList[I]; + if Assigned(OutRec.Pts) and (OutRec.FirstLeft = OldOutRec) then + begin + if Poly2ContainsPoly1(OutRec.Pts, NewOutRec.Pts, FUse64BitRange) then + OutRec.FirstLeft := NewOutRec; + end; + end; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.FixupFirstLefts2(OldOutRec, NewOutRec: POutRec); +var + I: Integer; +begin + for I := 0 to FPolyOutList.Count -1 do + with POutRec(fPolyOutList[I])^ do + if (FirstLeft = OldOutRec) then FirstLeft := NewOutRec; +end; +//------------------------------------------------------------------------------ + +procedure TClipper.JoinCommonEdges; +var + I, J, OKIdx, ObsoleteIdx: Integer; + Jr, Jr2: PJoinRec; + OutRec1, OutRec2, HoleStateRec: POutRec; + P1, P2: POutPt; +begin + for I := 0 to FJoinList.count -1 do + begin + Jr := FJoinList[I]; + + OutRec1 := FPolyOutList[Jr.Poly1Idx]; + OutRec2 := FPolyOutList[Jr.Poly2Idx]; + + if not Assigned(OutRec1.Pts) or not Assigned(OutRec2.Pts) then Continue; + + //get the polygon fragment with the correct hole state (FirstLeft) + //before calling JoinPoints() ... + if OutRec1 = OutRec2 then HoleStateRec := OutRec1 + else if Param1RightOfParam2(OutRec1, OutRec2) then HoleStateRec := OutRec2 + else if Param1RightOfParam2(OutRec2, OutRec1) then HoleStateRec := OutRec1 + else HoleStateRec := GetLowermostRec(OutRec1, OutRec2); + + if not JoinPoints(JR, P1, P2) then Continue; + + if (OutRec1 = OutRec2) then + begin + //instead of joining two polygons, we've just created a new one by + //splitting one polygon into two. + OutRec1.Pts := GetBottomPt(P1); + OutRec1.BottomPt := OutRec1.Pts; + OutRec1.BottomPt.Idx := OutRec1.Idx; + OutRec2 := CreateOutRec; + OutRec2.Idx := FPolyOutList.Add(OutRec2); + Jr.Poly2Idx := OutRec2.Idx; + OutRec2.Pts := GetBottomPt(P2); + OutRec2.BottomPt := OutRec2.Pts; + OutRec2.BottomPt.Idx := OutRec2.Idx; + + if Poly2ContainsPoly1(OutRec2.Pts, OutRec1.Pts, FUse64BitRange) then + begin + //OutRec2 is contained by OutRec1 ... + OutRec2.IsHole := not OutRec1.IsHole; + OutRec2.FirstLeft := OutRec1; + + //now fixup any subsequent joins that match the new polygon ... + FixupJoinRecs(Jr, P2, I + 1); + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if FUsingPolyTree then FixupFirstLefts2(OutRec2, OutRec1); + + FixupOutPolygon(OutRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(OutRec2); // but AFTER calling FixupJoinRecs() + + if (OutRec2.IsHole xor FReverseOutput) = (Area(OutRec2, FUse64BitRange) > 0) then + ReversePolyPtLinks(OutRec2.Pts); + end else if Poly2ContainsPoly1(OutRec1.Pts, OutRec2.Pts, FUse64BitRange) then + begin + //OutRec1 is contained by OutRec2 ... + OutRec2.IsHole := OutRec1.IsHole; + OutRec1.IsHole := not OutRec2.IsHole; + OutRec2.FirstLeft := OutRec1.FirstLeft; + OutRec1.FirstLeft := OutRec2; + + //now fixup any subsequent joins that match the new polygon ... + FixupJoinRecs(Jr, P2, I + 1); + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if FUsingPolyTree then FixupFirstLefts2(OutRec1, OutRec2); + + FixupOutPolygon(OutRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(OutRec2); // but AFTER calling PointIsVertex() + + if (OutRec1.IsHole xor FReverseOutput) = (Area(OutRec1, FUse64BitRange) > 0) then + ReversePolyPtLinks(OutRec1.Pts); + end else + begin + //the 2 polygons are completely separate ... + OutRec2.IsHole := OutRec1.IsHole; + OutRec2.FirstLeft := OutRec1.FirstLeft; + + //now fixup any subsequent joins that match the new polygon ... + FixupJoinRecs(Jr, P2, I + 1); + + //fixup FirstLeft pointers that may need reassigning to OutRec2 + if FUsingPolyTree then FixupFirstLefts1(OutRec1, OutRec2); + + FixupOutPolygon(OutRec1); //nb: do this AFTER calling PointIsVertex() + FixupOutPolygon(OutRec2); // in FixupJoinRecs() + end; + end else + begin + //joined 2 polygons together ... + + //cleanup edges ... + FixupOutPolygon(OutRec1); + + //delete the obsolete pointer ... + OKIdx := OutRec1.Idx; + ObsoleteIdx := OutRec2.Idx; + OutRec2.Pts := nil; + OutRec2.BottomPt := nil; + + OutRec1.IsHole := HoleStateRec.IsHole; + if HoleStateRec = OutRec2 then + OutRec1.FirstLeft := OutRec2.FirstLeft; + OutRec2.FirstLeft := OutRec1; + + //now fixup any subsequent joins ... + for J := I+1 to FJoinList.count -1 do + begin + Jr2 := FJoinList[J]; + if (Jr2.Poly1Idx = ObsoleteIdx) then Jr2.Poly1Idx := OKIdx; + if (Jr2.Poly2Idx = ObsoleteIdx) then Jr2.Poly2Idx := OKIdx; + end; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if FUsingPolyTree then FixupFirstLefts2(OutRec2, OutRec1); + end; + end; +end; + +//------------------------------------------------------------------------------ +// OffsetPolygons ... +//------------------------------------------------------------------------------ + +function GetUnitNormal(const Pt1, Pt2: TIntPoint): TDoublePoint; +var + Dx, Dy, F: Single; +begin + if (Pt2.X = Pt1.X) and (Pt2.Y = Pt1.Y) then + begin + Result.X := 0; + Result.Y := 0; + Exit; + end; + + Dx := (Pt2.X - Pt1.X); + Dy := (Pt2.Y - Pt1.Y); + F := 1 / Hypot(Dx, Dy); + Dx := Dx * F; + Dy := Dy * F; + Result.X := Dy; + Result.Y := -Dx +end; +//------------------------------------------------------------------------------ + +function BuildArc(const Pt: TIntPoint; A1, A2, R: Single): TPolygon; +var + I, N: Integer; + A, D: Double; + Steps: Int64; + S, C: Extended; //sin & cos +begin + Steps := Max(6, Round(Sqrt(Abs(R)) * Abs(A2 - A1))); + if Steps > $100 then Steps := $100; + SetLength(Result, Steps); + N := Steps - 1; + D := (A2 - A1) / N; + A := A1; + for I := 0 to N do + begin + SinCos(A, S, C); + Result[I].X := Pt.X + Round(C * R); + Result[I].Y := Pt.Y + Round(S * R); + A := A + D; + end; +end; +//------------------------------------------------------------------------------ + +function GetBounds(const Pts: TPolygons): TIntRect; +var + I,J: Integer; +begin + with Result do + begin + Left := HiRange; Top := HiRange; + Right := -HiRange; Bottom := -HiRange; + end; + for I := 0 to high(Pts) do + for J := 0 to high(Pts[I]) do + begin + if Pts[I][J].X < Result.Left then Result.Left := Pts[I][J].X; + if Pts[I][J].X > Result.Right then Result.Right := Pts[I][J].X; + if Pts[I][J].Y < Result.Top then Result.Top := Pts[I][J].Y; + if Pts[I][J].Y > Result.Bottom then Result.Bottom := Pts[I][J].Y; + end; + if Result.left = HiRange then + with Result do begin Left := 0; Top := 0; Right := 0; Bottom := 0; end; +end; +//------------------------------------------------------------------------------ + +function OffsetPolygons(const Polys: TPolygons; const Delta: Double; + JoinType: TJoinType = jtSquare; MiterLimit: Double = 2; + AutoFix: Boolean = True): TPolygons; +var + I, J, K, Len, OutLen, BotI: Integer; + Normals: TArrayOfDoublePoint; + R, RMin: Double; + Pt1, Pt2: TIntPoint; + Outer: TPolygon; + Bounds: TIntRect; + Pts: TPolygons; + BotPt: TIntPoint; +const + BuffLength: Integer = 128; + + procedure AddPoint(const Pt: TIntPoint); + var + Len: Integer; + begin + Len := length(Result[I]); + if OutLen = Len then + SetLength(Result[I], Len + BuffLength); + Result[I][OutLen] := Pt; + Inc(OutLen); + end; + + procedure DoSquare(mul: Double = 1.0); + var + A1, A2, Dx: Double; + begin + Pt1.X := round(Pts[I][J].X + Normals[K].X * Delta); + Pt1.Y := round(Pts[I][J].Y + Normals[K].Y * Delta); + Pt2.X := round(Pts[I][J].X + Normals[J].X * Delta); + Pt2.Y := round(Pts[I][J].Y + Normals[J].Y * Delta); + if ((Normals[K].X*Normals[J].Y-Normals[J].X*Normals[K].Y) * Delta >= 0) then + begin + A1 := ArcTan2(Normals[K].Y, Normals[K].X); + A2 := ArcTan2(-Normals[J].Y, -Normals[J].X); + A1 := abs(A2 - A1); + if A1 > pi then A1 := pi*2 - A1; + Dx := tan((pi - A1)/4) * abs(Delta*mul); + + Pt1 := IntPoint(round(Pt1.X -Normals[K].Y * Dx), + round(Pt1.Y + Normals[K].X * Dx)); + AddPoint(Pt1); + Pt2 := IntPoint(round(Pt2.X + Normals[J].Y * Dx), + round(Pt2.Y - Normals[J].X * Dx)); + AddPoint(Pt2); + end else + begin + AddPoint(Pt1); + AddPoint(Pts[I][J]); + AddPoint(Pt2); + end; + end; + + procedure DoMiter; + var + Q: Double; + begin + if ((Normals[K].X*Normals[J].Y-Normals[J].X*Normals[K].Y)*Delta >= 0) then + begin + Q := Delta / R; + AddPoint(IntPoint(round(Pts[I][J].X + (Normals[K].X + Normals[J].X) *Q), + round(Pts[I][J].Y + (Normals[K].Y + Normals[J].Y) *Q))); + end else + begin + Pt1.X := round(Pts[I][J].X + Normals[K].X * Delta); + Pt1.Y := round(Pts[I][J].Y + Normals[K].Y * Delta); + Pt2.X := round(Pts[I][J].X + Normals[J].X * Delta); + Pt2.Y := round(Pts[I][J].Y + Normals[J].Y * Delta); + AddPoint(Pt1); + AddPoint(Pts[I][J]); + AddPoint(Pt2); + end; + end; + + procedure DoRound; + var + M: Integer; + Arc: TPolygon; + A1, A2: Double; + begin + Pt1.X := round(Pts[I][J].X + Normals[K].X * Delta); + Pt1.Y := round(Pts[I][J].Y + Normals[K].Y * Delta); + Pt2.X := round(Pts[I][J].X + Normals[J].X * Delta); + Pt2.Y := round(Pts[I][J].Y + Normals[J].Y * Delta); + AddPoint(Pt1); + //round off reflex angles (ie > 180 deg) unless almost flat (ie < 10deg). + //(N1.X * N2.Y - N2.X * N1.Y) == unit normal "cross product" == sin(angle) + //(N1.X * N2.X + N1.Y * N2.Y) == unit normal "dot product" == cos(angle) + //dot product Normals == 1 -> no angle + if ((Normals[K].X*Normals[J].Y - Normals[J].X*Normals[K].Y)*Delta >= 0) then + begin + if ((Normals[J].X*Normals[K].X+Normals[J].Y*Normals[K].Y) < 0.985) then + begin + A1 := ArcTan2(Normals[K].Y, Normals[K].X); + A2 := ArcTan2(Normals[J].Y, Normals[J].X); + if (Delta > 0) and (A2 < A1) then A2 := A2 + pi*2 + else if (Delta < 0) and (A2 > A1) then A2 := A2 - pi*2; + Arc := BuildArc(Pts[I][J], A1, A2, Delta); + for M := 0 to high(Arc) do + AddPoint(Arc[M]); + end; + end else + AddPoint(Pts[I][J]); + AddPoint(Pt2); + end; + + function UpdateBotPt(const Pt: TIntPoint; var BotPt: TIntPoint): Boolean; + begin + if (pt.Y > BotPt.Y) or ((pt.Y = BotPt.Y) and (Pt.X < BotPt.X)) then + begin + BotPt := Pt; + Result := True; + end + else Result := False; + end; + +begin + Result := nil; + + //AutoFix - fixes polygon orientation if necessary and removes + //duplicate vertices. Can be set False when you're sure that polygon + //orientation is correct and that there are no duplicate vertices. + if AutoFix then + begin + Len := Length(Polys); + SetLength(Pts, Len); + BotI := 0; //index of outermost polygon + while (BotI < Len) and (Length(Polys[BotI]) = 0) do Inc(BotI); + if (BotI = Len) then Exit; + BotPt := Polys[BotI][0]; + for I := BotI to Len - 1 do + begin + Len := Length(Polys[I]); + SetLength(Pts[I], Len); + if Len = 0 then Continue; + Pts[I][0] := Polys[I][0]; + if UpdateBotPt(Pts[I][0], BotPt) then BotI := I; + K := 0; + for J := 1 to Len - 1 do + if not PointsEqual(Pts[I][K], Polys[I][J]) then + begin + Inc(K); + Pts[I][K] := Polys[I][J]; + if UpdateBotPt(Pts[I][K], BotPt) then BotI := I; + end; + if K + 1 < Len then + SetLength(Pts[I], K + 1); + end; + if not Orientation(Pts[BotI]) then + Pts := ReversePolygons(Pts); + end else + Pts := Polys; + + //MiterLimit defaults to twice Delta's width ... + if MiterLimit <= 1 then MiterLimit := 1; + RMin := 2/(sqr(MiterLimit)); + + SetLength(Result, length(Pts)); + for I := 0 to high(Pts) do + begin + Result[I] := nil; + Len := length(Pts[I]); + if (Len > 1) and (Pts[I][0].X = Pts[I][Len - 1].X) and + (Pts[I][0].Y = Pts[I][Len - 1].Y) then Dec(Len); + + if (Len = 0) or ((Len < 3) and (Delta <= 0)) then + Continue + else if (Len = 1) then + begin + Result[I] := BuildArc(Pts[I][0], 0, 2*pi, Delta); + Continue; + end; + + //build Normals ... + SetLength(Normals, Len); + for J := 0 to Len-2 do + Normals[J] := GetUnitNormal(Pts[I][J], Pts[I][J+1]); + Normals[Len-1] := GetUnitNormal(Pts[I][Len-1], Pts[I][0]); + + OutLen := 0; + K := Len -1; + for J := 0 to Len-1 do + begin + case JoinType of + jtMiter: + begin + R := 1 + (Normals[J].X*Normals[K].X + Normals[J].Y*Normals[K].Y); + if (R >= RMin) then + DoMiter else + DoSquare(MiterLimit); + end; + jtSquare: DoSquare; + jtRound: DoRound; + end; + K := J; + end; + SetLength(Result[I], OutLen); + end; + + //finally, clean up untidy corners ... + with TClipper.Create do + try + AddPolygons(Result, ptSubject); + if Delta > 0 then + begin + Execute(ctUnion, Result, pftPositive, pftPositive); + end else + begin + Bounds := GetBounds(Result); + SetLength(Outer, 4); + Outer[0] := IntPoint(Bounds.left-10, Bounds.bottom+10); + Outer[1] := IntPoint(Bounds.right+10, Bounds.bottom+10); + Outer[2] := IntPoint(Bounds.right+10, Bounds.top-10); + Outer[3] := IntPoint(Bounds.left-10, Bounds.top-10); + AddPolygon(Outer, ptSubject); + Execute(ctUnion, Result, pftNegative, pftNegative); + //delete the outer rectangle ... + Len := length(Result); + for J := 1 to Len -1 do Result[J-1] := Result[J]; + if Len > 0 then + SetLength(Result, Len -1); + //restore polygon orientation ... + Result := ReversePolygons(Result); + end; + finally + free; + end; +end; +//------------------------------------------------------------------------------ + +function SimplifyPolygon(const poly: TPolygon; FillType: TPolyFillType = pftEvenOdd): TPolygons; +begin + with TClipper.Create do + try + AddPolygon(poly, ptSubject); + Execute(ctUnion, Result, FillType, FillType); + finally + free; + end; +end; +//------------------------------------------------------------------------------ + +function SimplifyPolygons(const polys: TPolygons; FillType: TPolyFillType = pftEvenOdd): TPolygons; +begin + with TClipper.Create do + try + AddPolygons(polys, ptSubject); + Execute(ctUnion, Result, FillType, FillType); + finally + free; + end; +end; +//------------------------------------------------------------------------------ + +function CleanPolygon(Poly: TPolygon; Distance: double = 1.415): TPolygon; +var + D, I, J, Len: Integer; + Ip: TIntPoint; +begin + //Delta = proximity in units/pixels below which vertices + //will be stripped. Default ~= sqrt(2) so when adjacent + //vertices have both x & y coords within 1 unit, then + //the second vertex will be stripped. + Len := Length(Poly); + if (Len < 3) then + begin + Result := nil; + Exit; + end; + SetLength(Result, Len); + D := Round(Distance * Distance); + Ip := Poly[0]; + J := 1; + for I := 1 to Len -1 do + begin + if ((Poly[I].X - Ip.X) * (Poly[I].X - Ip.X) + + (Poly[I].Y - Ip.Y) * (Poly[I].Y - Ip.Y) <= D) then + continue; + Result[J] := Poly[I]; + Ip := Poly[I]; + inc(J); + end; + Ip := Poly[J - 1]; + if ((Poly[0].X - Ip.X) * (Poly[0].X - Ip.X) + + (Poly[0].Y - Ip.Y) * (Poly[0].Y - Ip.Y) <= D) then dec(J); + if (J < Len) then SetLength(Result, J); +end; +//------------------------------------------------------------------------------ + +function CleanPolygons(Polys: TPolygons; Distance: double = 1.415): TPolygons; +var + I, Len: Integer; +begin + Len := Length(Polys); + SetLength(Result, Len); + for I := 0 to Len - 1 do + Result[I] := CleanPolygon(Polys[I], Distance); +end; +//------------------------------------------------------------------------------ + +procedure AddPolyNodeToPolygons(PolyNode: TPolyNode; var Polygons: TPolygons); +var + I: Integer; +begin + if Length(PolyNode.Contour) > 0 then + begin + I := Length(Polygons); + SetLength(Polygons, I +1); + Polygons[I] := PolyNode.Contour; + end; + for I := 0 to PolyNode.ChildCount - 1 do + AddPolyNodeToPolygons(PolyNode.Childs[I], Polygons); +end; +//------------------------------------------------------------------------------ + +function PolyTreeToPolygons(PolyTree: TPolyTree): TPolygons; +begin + Result := nil; + AddPolyNodeToPolygons(PolyTree, Result); +end; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +end. diff --git a/clipper/Delphi/main demo/GR32_Misc.pas b/clipper/Delphi/main demo/GR32_Misc.pas new file mode 100755 index 0000000..48c4f95 --- /dev/null +++ b/clipper/Delphi/main demo/GR32_Misc.pas @@ -0,0 +1,293 @@ +unit GR32_Misc; + +(******************************************************************************* +* * +* Author : Angus Johnson * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +*******************************************************************************) + +interface + +{$WARN UNSAFE_CODE OFF} + +uses + Windows, Types, + Classes, SysUtils, Math, GR32, GR32_LowLevel, GR32_Blend, GR32_Transforms, + Graphics, GR32_Math, GR32_Polygons, GR32_PolygonsEx, GR32_VPR; + +type + TArrayOfArrayOfArrayOfFixedPoint = array of TArrayOfArrayOfFixedPoint; + +function CreateMaskFromPolygon(bitmap: TBitmap32; + const polygons: TArrayOfArrayOfFloatPoint; + fillMode: TPolyFillMode = pfAlternate): TBitmap32; overload; +procedure ApplyMask(modifiedBmp, originalBmp, maskBmp: TBitmap32; invertMask: boolean = false); +procedure Simple3D(bitmap: TBitmap32; const pts: TArrayOfArrayOfFloatPoint; + dx,dy,fadeRate: integer; topLeftColor, bottomRightColor: TColor32; + fillMode: TPolyFillMode = pfAlternate); +function GetEllipsePoints(const ellipseRect: TFloatRect): TArrayOfFloatPoint; + +const + MAXIMUM_SHADOW_FADE = 0; + MEDIUM_SHADOW_FADE = 5; + MINIMUM_SHADOW_FADE = 10; + NO_SHADOW_FADE = 11; //anything > 10 + +implementation + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +procedure OffsetPoints(var pts: TArrayOfFloatPoint; dx, dy: single); +var + i: integer; +begin + for i := 0 to high(pts) do + with pts[i] do + begin + X := X + dx; + Y := Y + dy; + end; +end; +//------------------------------------------------------------------------------ + +function CreateMaskFromPolygon(bitmap: TBitmap32; + const polygons: TArrayOfArrayOfFloatPoint; + fillMode: TPolyFillMode = pfAlternate): TBitmap32; +var + highI: integer; +begin + result := TBitmap32.create; + with bitmap do result.SetSize(width,height); + highI := high(polygons); + if highI < 0 then exit; + PolyPolygonFS(result, polygons, clWhite32, fillMode); + PolyPolyLineFS(result, polygons, clBlack32, true); +end; +//------------------------------------------------------------------------------ + +procedure ApplyMask(modifiedBmp, originalBmp, maskBmp: TBitmap32; invertMask: boolean = false); +var + i: integer; + origClr, modClr, mskClr: PColor32Entry; +begin + if not assigned(originalBmp) or not assigned(maskBmp) or + (originalBmp.Width <> modifiedBmp.Width) or + (originalBmp.Height <> modifiedBmp.Height) or + (originalBmp.Height <> maskBmp.Height) or + (originalBmp.Height <> maskBmp.Height) then exit; + + origClr := @originalBmp.Bits[0]; + modClr := @modifiedBmp.Bits[0]; + mskClr := @maskBmp.Bits[0]; + for i := 1 to originalBmp.Width * originalBmp.Height do + begin + //black pixel in mask -> replace modified color with original color + //white pixel in mask -> keep modified color + if invertMask then + MergeMemEx(origClr.ARGB, modClr.ARGB, 255- mskClr.B) else + MergeMemEx(origClr.ARGB, modClr.ARGB, mskClr.B); + inc(origClr); + inc(modClr); + inc(mskClr); + end; + EMMS; +end; +//------------------------------------------------------------------------------ + +procedure SimpleShadow(bitmap: TBitmap32; const pts: TArrayOfArrayOfFloatPoint; + dx, dy, fadeRate: integer; shadowColor: TColor32; + closed: boolean = false; NoInteriorBleed: boolean = false; + fillMode: TPolyFillMode = pfAlternate); +var + i, j, maxD: integer; + sx,sy, a, alpha, alphaLinear, alphaExp, dRate: single; + p: TArrayOfFloatPoint; + originalBitmap, maskBitmap: TBitmap32; + sc: TColor32; +begin + if ((dx = 0) and (dy = 0)) or (length(pts) = 0) then exit; + + if abs(dy) > abs(dx) then + begin + maxD := abs(dy); + sy := sign(dy); + sx := dx/maxD; + end else + begin + maxD := abs(dx); + sx := sign(dx); + sy := dy/maxD; + end; + + if fadeRate <= MAXIMUM_SHADOW_FADE then dRate := 0.05 + else if fadeRate >= MINIMUM_SHADOW_FADE then dRate := 0.95 + else dRate := fadeRate/10; + alpha := AlphaComponent(shadowColor); + alphaLinear := alpha*dRate/maxD; + alphaExp := exp(ln(dRate)/maxD); + + NoInteriorBleed := NoInteriorBleed and closed; + if NoInteriorBleed then + begin + originalBitmap := TBitmap32.Create; + originalBitmap.Assign(bitmap); + maskBitmap := CreateMaskFromPolygon(bitmap,pts, fillMode); + end else + begin + originalBitmap := nil; + maskBitmap := nil; + end; + + try + a := alpha; + sc := shadowColor; + for j := 0 to high(pts) do + begin + alpha := a; + shadowColor := sc; + p := copy(pts[j], 0, length(pts[j])); + for i := 1 to maxD do + begin + PolyLineFS(bitmap, p, shadowColor, closed); + alpha := alpha * alphaExp; + if fadeRate < NO_SHADOW_FADE then + shadowColor := SetAlpha(shadowColor, round(alpha - i*alphaLinear)); + OffsetPoints(p, sx, sy); + end; + end; + if assigned(originalBitmap) then + ApplyMask(bitmap, originalBitmap, maskBitmap); + finally + FreeAndNil(originalBitmap); + FreeAndNil(maskBitmap); + end; +end; +//------------------------------------------------------------------------------ + +procedure Simple3D(bitmap: TBitmap32; const pts: TArrayOfArrayOfFloatPoint; + dx,dy,fadeRate: integer; topLeftColor, bottomRightColor: TColor32; + fillMode: TPolyFillMode = pfAlternate); overload; +var + mask, orig: TBitmap32; +begin + orig := TBitmap32.Create; + mask := CreateMaskFromPolygon(bitmap,pts, fillMode); + try + orig.Assign(bitmap); + SimpleShadow(bitmap, pts, -dx, -dy, fadeRate, bottomRightColor, true); + SimpleShadow(bitmap, pts, dx, dy, fadeRate, topLeftColor, true); + ApplyMask(bitmap, orig, mask, true); + finally + orig.Free; + mask.Free; + end; +end; +//------------------------------------------------------------------------------ + +function GetCBezierPoints(const control_points: array of TFloatPoint): TArrayOfFloatPoint; +var + i, j, arrayLen, resultCnt: integer; + ctrlPts: array [ 0..3] of TFloatPoint; +const + cbezier_tolerance = 0.5; + half = 0.5; + + procedure RecursiveCBezier(const p1, p2, p3, p4: TFloatPoint); + var + p12, p23, p34, p123, p234, p1234: TFloatPoint; + begin + //assess flatness of curve ... + //http://groups.google.com/group/comp.graphics.algorithms/tree/browse_frm/thread/d85ca902fdbd746e + if abs(p1.x + p3.x - 2*p2.x) + abs(p2.x + p4.x - 2*p3.x) + + abs(p1.y + p3.y - 2*p2.y) + abs(p2.y + p4.y - 2*p3.y) < cbezier_tolerance then + begin + if resultCnt = length(result) then + setLength(result, length(result) +128); + result[resultCnt] := p4; + inc(resultCnt); + end else + begin + p12.X := (p1.X + p2.X) *half; + p12.Y := (p1.Y + p2.Y) *half; + p23.X := (p2.X + p3.X) *half; + p23.Y := (p2.Y + p3.Y) *half; + p34.X := (p3.X + p4.X) *half; + p34.Y := (p3.Y + p4.Y) *half; + p123.X := (p12.X + p23.X) *half; + p123.Y := (p12.Y + p23.Y) *half; + p234.X := (p23.X + p34.X) *half; + p234.Y := (p23.Y + p34.Y) *half; + p1234.X := (p123.X + p234.X) *half; + p1234.Y := (p123.Y + p234.Y) *half; + RecursiveCBezier(p1, p12, p123, p1234); + RecursiveCBezier(p1234, p234, p34, p4); + end; + end; + +begin + //first check that the 'control_points' count is valid ... + arrayLen := length(control_points); + if (arrayLen < 4) or ((arrayLen -1) mod 3 <> 0) then exit; + + setLength(result, 128); + result[0] := control_points[0]; + resultCnt := 1; + for i := 0 to (arrayLen div 3)-1 do + begin + for j := 0 to 3 do + ctrlPts[j] := control_points[i*3 +j]; + RecursiveCBezier(ctrlPts[0], ctrlPts[1], ctrlPts[2], ctrlPts[3]); + end; + SetLength(result,resultCnt); +end; +//------------------------------------------------------------------------------ + +function GetEllipsePoints(const ellipseRect: TFloatRect): TArrayOfFloatPoint; +const + //Magic constant = + // 2/3*(1-cos(90deg/2))/sin(90deg/2) = 2/3*(sqrt(2)-1) = 0.276142375 ... + offset: single = 0.276142375; +var + midx, midy, offx, offy: single; + pts: array [0..12] of TFloatPoint; +begin + with ellipseRect do + begin + if (abs(Left - Right) <= 0.5) and (abs(Top - Bottom) <= 0.5) then + begin + setlength(result,1); + result[0] := FloatPoint(Left,Top); + exit; + end; + + midx := (right + left)/2; + midy := (bottom + top)/2; + offx := (right - left) * offset; + offy := (bottom - top) * offset; + //draws an ellipse starting at angle 0 and moving anti-clockwise ... + pts[0] := FloatPoint(right, midy); + pts[1] := FloatPoint(right, midy - offy); + pts[2] := FloatPoint(midx + offx, top); + pts[3] := FloatPoint(midx, top); + pts[4] := FloatPoint(midx - offx, top); + pts[5] := FloatPoint(left, midy - offy); + pts[6] := FloatPoint(left, midy); + pts[7] := FloatPoint(left, midy + offy); + pts[8] := FloatPoint(midx - offx, bottom); + pts[9] := FloatPoint(midx, bottom); + pts[10] := FloatPoint(midx + offx, bottom); + pts[11] := FloatPoint(right, midy + offy); + pts[12] := pts[0]; + end; + result := GetCBezierPoints(pts); +end; +//------------------------------------------------------------------------------ + +end. diff --git a/clipper/Delphi/main demo/clipper_demo.dpr b/clipper/Delphi/main demo/clipper_demo.dpr new file mode 100755 index 0000000..54ab066 --- /dev/null +++ b/clipper/Delphi/main demo/clipper_demo.dpr @@ -0,0 +1,16 @@ +program clipper_demo; + +uses + Forms, + main in 'main.pas' {MainForm}, + GR32_Misc in 'GR32_Misc.pas', + GR32_VPR in 'GR32_VPR.pas', + clipper in '..\clipper.pas'; + +{$R *.res} + +begin + Application.Initialize; + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. diff --git a/clipper/Delphi/main demo/clipper_demo.exe b/clipper/Delphi/main demo/clipper_demo.exe new file mode 100755 index 0000000..af11a87 Binary files /dev/null and b/clipper/Delphi/main demo/clipper_demo.exe differ diff --git a/clipper/Delphi/main demo/clipper_demo.res b/clipper/Delphi/main demo/clipper_demo.res new file mode 100755 index 0000000..99250a0 Binary files /dev/null and b/clipper/Delphi/main demo/clipper_demo.res differ diff --git a/clipper/Delphi/main demo/main.dfm b/clipper/Delphi/main demo/main.dfm new file mode 100755 index 0000000..e3fb4ac --- /dev/null +++ b/clipper/Delphi/main demo/main.dfm @@ -0,0 +1,289 @@ +object MainForm: TMainForm + Left = 235 + Top = 110 + Width = 731 + Height = 547 + Caption = 'Clipper Delphi Demo' + Color = clBtnFace + Font.Charset = ARABIC_CHARSET + Font.Color = clWindowText + Font.Height = -12 + Font.Name = 'Arial' + Font.Style = [] + KeyPreview = True + OldCreateOrder = False + Position = poDesktopCenter + OnCreate = FormCreate + OnKeyPress = FormKeyPress + OnMouseWheel = FormMouseWheel + OnResize = FormResize + PixelsPerInch = 96 + TextHeight = 15 + object Panel1: TPanel + Left = 0 + Top = 0 + Width = 183 + Height = 501 + Align = alLeft + TabOrder = 0 + object lblClipOpacity: TLabel + Left = 17 + Top = 408 + Width = 100 + Height = 15 + Caption = 'Clip Opacity (255):' + FocusControl = tbClipOpacity + end + object lblSubjOpacity: TLabel + Left = 17 + Top = 365 + Width = 103 + Height = 15 + Caption = 'Subj &Opacity (255):' + FocusControl = tbSubjOpacity + end + object GroupBox1: TGroupBox + Left = 13 + Top = 8 + Width = 159 + Height = 115 + Caption = 'Clipping Oper&ation' + TabOrder = 0 + object rbIntersection: TRadioButton + Left = 14 + Top = 38 + Width = 113 + Height = 17 + Caption = 'Intersection' + Checked = True + TabOrder = 1 + TabStop = True + OnClick = rbIntersectionClick + end + object rbUnion: TRadioButton + Left = 14 + Top = 56 + Width = 113 + Height = 17 + Caption = 'Union' + TabOrder = 2 + OnClick = rbIntersectionClick + end + object rbDifference: TRadioButton + Left = 14 + Top = 74 + Width = 113 + Height = 17 + Caption = 'Difference' + TabOrder = 3 + OnClick = rbIntersectionClick + end + object rbXOR: TRadioButton + Left = 14 + Top = 92 + Width = 113 + Height = 17 + Caption = 'XOR' + TabOrder = 4 + OnClick = rbIntersectionClick + end + object rbNone: TRadioButton + Left = 14 + Top = 20 + Width = 113 + Height = 17 + Caption = 'None' + TabOrder = 0 + OnClick = rbIntersectionClick + end + end + object rbStatic: TRadioButton + Left = 16 + Top = 129 + Width = 115 + Height = 17 + Caption = '&Static Polygons' + Checked = True + TabOrder = 1 + TabStop = True + OnClick = rbStaticClick + end + object bExit: TButton + Left = 109 + Top = 453 + Width = 52 + Height = 25 + Cancel = True + Caption = 'E&xit' + TabOrder = 7 + OnClick = bExitClick + end + object gbRandom: TGroupBox + Left = 11 + Top = 184 + Width = 159 + Height = 169 + TabOrder = 4 + object lblSubjCount: TLabel + Left = 4 + Top = 40 + Width = 129 + Height = 15 + Caption = 'No. Subject edges: (20)' + Enabled = False + FocusControl = tbSubj + end + object lblClipCount: TLabel + Left = 4 + Top = 87 + Width = 110 + Height = 15 + Caption = 'No. Clip edges (20):' + Enabled = False + FocusControl = tbClip + end + object tbSubj: TTrackBar + Left = 5 + Top = 58 + Width = 145 + Height = 28 + Enabled = False + Max = 100 + Min = 3 + Position = 20 + TabOrder = 2 + ThumbLength = 16 + TickStyle = tsNone + OnChange = tbSubjChange + end + object tbClip: TTrackBar + Left = 5 + Top = 106 + Width = 145 + Height = 28 + Enabled = False + Max = 100 + Min = 3 + Position = 20 + TabOrder = 3 + ThumbLength = 16 + TickStyle = tsNone + OnChange = tbSubjChange + end + object bNext: TButton + Left = 10 + Top = 132 + Width = 134 + Height = 25 + Caption = '&New Polygons' + TabOrder = 4 + OnClick = bNextClick + end + object rbEvenOdd: TRadioButton + Left = 5 + Top = 14 + Width = 73 + Height = 17 + Caption = 'E&venOdd' + Checked = True + Enabled = False + TabOrder = 0 + TabStop = True + OnClick = rbEvenOddClick + end + object rbNonZero: TRadioButton + Left = 82 + Top = 14 + Width = 69 + Height = 17 + Caption = 'Non&Zero' + Enabled = False + TabOrder = 1 + OnClick = rbEvenOddClick + end + end + object rbRandom1: TRadioButton + Left = 16 + Top = 146 + Width = 146 + Height = 17 + Caption = 'Random Polygons &1' + TabOrder = 2 + OnClick = rbStaticClick + end + object tbClipOpacity: TTrackBar + Left = 12 + Top = 425 + Width = 158 + Height = 28 + Max = 255 + Position = 255 + TabOrder = 6 + ThumbLength = 16 + TickStyle = tsNone + OnChange = tbClipOpacityChange + end + object tbSubjOpacity: TTrackBar + Left = 12 + Top = 382 + Width = 158 + Height = 28 + Max = 255 + Position = 255 + TabOrder = 5 + ThumbLength = 16 + TickStyle = tsNone + OnChange = tbSubjOpacityChange + end + object rbRandom2: TRadioButton + Left = 16 + Top = 164 + Width = 146 + Height = 17 + Caption = 'Random Polygons &2' + TabOrder = 3 + OnClick = rbStaticClick + end + object bSaveSvg: TButton + Left = 19 + Top = 453 + Width = 82 + Height = 25 + Cancel = True + Caption = 'Save S&VG ...' + TabOrder = 8 + OnClick = bSaveSvgClick + end + end + object StatusBar1: TStatusBar + Left = 0 + Top = 501 + Width = 723 + Height = 19 + Panels = <> + SimplePanel = True + end + object ImgView321: TImgView32 + Left = 183 + Top = 0 + Width = 540 + Height = 501 + Align = alClient + Bitmap.ResamplerClassName = 'TNearestResampler' + BitmapAlign = baCustom + Scale = 1.000000000000000000 + ScaleMode = smScale + ScrollBars.ShowHandleGrip = True + ScrollBars.Style = rbsDefault + OverSize = 0 + TabOrder = 2 + OnDblClick = bNextClick + OnResize = ImgView321Resize + end + object SaveDialog1: TSaveDialog + DefaultExt = 'svg' + Filter = 'SVG Files (*.svg)|*.svg' + Left = 239 + Top = 32 + end +end diff --git a/clipper/Delphi/main demo/main.pas b/clipper/Delphi/main demo/main.pas new file mode 100755 index 0000000..d59fce0 --- /dev/null +++ b/clipper/Delphi/main demo/main.pas @@ -0,0 +1,789 @@ +unit main; + +(******************************************************************************* +* * +* Author : Angus Johnson * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2011 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +*******************************************************************************) + +interface + +uses + Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, + Dialogs, StdCtrls, ComCtrls, ExtCtrls, Math, + GR32, GR32_Image, GR32_Polygons, //http://sourceforge.net/projects/graphics32/ + GR32_PolygonsEx, GR32_VPR, //http://sourceforge.net/projects/vpr/ + GR32_Misc, clipper; + +type + + //SVG Builder structures //////////////////////////////////////// + + TStyleInfo = record + pft: TPolyFillType; + brushClr: TColor32; + penClr: TColor32; + dashArray: TArrayOfInteger; + penWidth: double; + showCoords: boolean; + end; + + TPolyInfo = record + polygons: TPolygons; + si: TStyleInfo; + end; + TPolyInfos = array of TPolyInfo; + + TTextInfo = record + text: string; + x,y: integer; + fontName: string; + fontSize: integer; + fontColor: string; + end; + TTextInfos = array of TTextInfo; + + ///////////////////////////////////////////////////////////////// + + TSvgBuilder = class + private + polyList: TPolyInfos; + textList: TTextInfos; + function Color32ToHtml(clr: TColor32): string; + public + fontName: string; + fontSize: integer; + fontColor: TColor32; + style: TStyleInfo; + constructor Create; + procedure Clear; + procedure AddPolygons(const poly: TPolygons); + procedure AddText(const text: string; X,Y: integer); + function SaveToFile(filename: string; + scale: double = 1.0; margin: integer = 10): boolean; + end; + + TMainForm = class(TForm) + Panel1: TPanel; + StatusBar1: TStatusBar; + ImgView321: TImgView32; + GroupBox1: TGroupBox; + rbIntersection: TRadioButton; + rbUnion: TRadioButton; + rbDifference: TRadioButton; + rbXOR: TRadioButton; + rbStatic: TRadioButton; + bExit: TButton; + rbNone: TRadioButton; + gbRandom: TGroupBox; + lblSubjCount: TLabel; + lblClipCount: TLabel; + tbSubj: TTrackBar; + tbClip: TTrackBar; + rbRandom1: TRadioButton; + bNext: TButton; + tbClipOpacity: TTrackBar; + lblClipOpacity: TLabel; + lblSubjOpacity: TLabel; + tbSubjOpacity: TTrackBar; + rbRandom2: TRadioButton; + rbEvenOdd: TRadioButton; + rbNonZero: TRadioButton; + bSaveSvg: TButton; + SaveDialog1: TSaveDialog; + procedure FormCreate(Sender: TObject); + procedure ImgView321Resize(Sender: TObject); + procedure rbIntersectionClick(Sender: TObject); + procedure FormResize(Sender: TObject); + procedure tbSubjChange(Sender: TObject); + procedure bNextClick(Sender: TObject); + procedure bExitClick(Sender: TObject); + procedure tbClipOpacityChange(Sender: TObject); + procedure rbStaticClick(Sender: TObject); + procedure tbSubjOpacityChange(Sender: TObject); + procedure rbEvenOddClick(Sender: TObject); + procedure FormMouseWheel(Sender: TObject; Shift: TShiftState; + WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean); + procedure FormKeyPress(Sender: TObject; var Key: Char); + procedure bSaveSvgClick(Sender: TObject); + private + offsetMul2: integer; + function GetFillTypeI: TPolyFillType; + function GetOpTypeI: TClipType; + procedure ShowStaticPolys; + procedure ShowRandomPolys1(newPoly: boolean); + procedure ShowRandomPolys2(newPoly: boolean); + procedure RePaintBitmapI; + public + { Public declarations } + end; + +var + MainForm: TMainForm; + +implementation + +//------------------------------------------------------------------------------ +// TSvgBuilder +//------------------------------------------------------------------------------ + +constructor TSvgBuilder.Create; +begin + fontName := 'Verdana'; + fontSize := 12; + fontColor := clBlack32; + style.brushClr := clWhite32; + style.penClr := clBlack32; + style.penWidth := 1.5; +end; +//------------------------------------------------------------------------------ + +procedure TSvgBuilder.Clear; +begin + polyList := nil; + textList := nil; +end; +//------------------------------------------------------------------------------ + +function TSvgBuilder.Color32ToHtml(clr: TColor32): string; +begin + with TColor32Entry(clr) do + result := format('#%.2x%.2x%.2x', [R, G, B]) ; +end; +//------------------------------------------------------------------------------ + +function IntArrayToStr(a: TArrayOfInteger; scale: double = 1.0; + dx: integer = 0; dy: integer = 0): string; +var + i: integer; +begin + result := format('%1.1n',[a[0] * scale + dx]); + for i := 1 to high(a) do + if odd(i) then + result := result + format(', %1.1n',[a[i] * scale + dy]) else + result := result + format(', %1.1n',[a[i] * scale + dx]); +end; +//------------------------------------------------------------------------------ + +procedure TSvgBuilder.AddPolygons(const poly: TPolygons); +var + i, len: integer; +begin + i := length(poly); + if i = 0 then Exit; + len := length(polyList); + setlength(polyList, len+1); + setlength(polyList[len].polygons, i); + for i := 0 to i-1 do + polyList[len].polygons[i] := Copy(poly[i], 0, MaxInt); + polyList[len].si.pft := style.pft; + polyList[len].si.brushClr := style.brushClr; + polyList[len].si.penClr := style.penClr; + polyList[len].si.dashArray := copy(style.dashArray, 0, maxint); + polyList[len].si.penWidth := style.penWidth; + polyList[len].si.showCoords := style.showCoords; +end; +//------------------------------------------------------------------------------ + +procedure TSvgBuilder.AddText(const text: string; X,Y: integer); +var + len: integer; +begin + len := length(textList); + setlength(textList, len +1); + textList[len].text := text; + textList[len].x := X; + textList[len].y := Y; + textList[len].fontName := fontName; + textList[len].fontSize := fontSize; + textList[len].fontColor := Color32ToHtml(fontColor); +end; +//------------------------------------------------------------------------------ + +function TSvgBuilder.SaveToFile(filename: string; + scale: double = 1.0; margin: integer = 10): boolean; +const + pft_string: array[TPolyFillType] of string = ('evenodd', 'nonzero', 'positive', 'negative'); + svg_xml_start: array [0..1] of string = + (''+#10+ + ''+#10+#10+''+#10+#10); + poly_start: string = ' 1 then + dashArrayStr := 'stroke-dasharray:'+ IntArrayToStr(dashArray) +';' + else + dashArrayStr := ''; + ss.WriteString(format('"'#10+ + ' style="fill:%s; fill-opacity:%1.2n; fill-rule:%s;'#10+ + ' stroke:%s; stroke-opacity:%1.2n; %s stroke-width:%1.2n;"/>'#10#10, + [Color32ToHtml(brushClr), AlphaComponent(brushClr)/255, + pft_string[pft], Color32ToHtml(penClr), AlphaComponent(penClr)/255, + dashArrayStr, penWidth])); + end; + + if polyList[i].si.showCoords then + begin + ss.WriteString(''#10#10); + for j := 0 to high(polyList[i].polygons) do + begin + if (length(polyList[i].polygons[j]) < 3) then continue; + for k := 0 to high(polyList[i].polygons[j]) do + with polyList[i].polygons[j][k] do + ss.WriteString(format('%1.0n,%1.0n'#10, + [X * scale + offsetX, Y * scale + offsetY, X, Y])); + ss.WriteString(#10); + end; + ss.WriteString(''#10); + end; + end; + + for i := 0 to high(textList) do + with textList[i] do + begin + if fontSize < 7 then fontSize := 7 else if fontSize > 30 then fontSize := 30; + ss.WriteString(format(''#10, + [fontName, fontSize, fontColor])); + ss.WriteString(format('%s'#10''#10#10, + [X * scale + offsetX, Y * scale + offsetY, text])); + end; + + ss.WriteString(svg_xml_end); + //finally write to file ... + with TFileStream.Create(filename, fmCreate) do + try CopyFrom(ss, 0); finally free; end; + finally + ss.Free; + DecimalSeparator := ds; + end; + +end; + +//------------------------------------------------------------------------------ +// Miscellaneous functions ... +//------------------------------------------------------------------------------ + +const + subjPenColor: TColor32 = $60C3C9CF; + subjBrushColor: TColor32 = $00DDDDF0; + clipPenColor: TColor32 = $30F9BEA6; + clipBrushColor: TColor32 = $00FFE0E0; + solPenColor: TColor32 = $7F003300; + solBrushColor: TColor32 = $8066EF7F; + +var + scale: integer = 1; //scale bitmap to 10 decimal places + subj: TArrayOfArrayOfFloatPoint = nil; + clip: TArrayOfArrayOfFloatPoint = nil; + subjI: TPolygons = nil; + clipI: TPolygons = nil; + solution: TArrayOfArrayOfFloatPoint = nil; + solutionI: TPolygons = nil; + subjOpacity: cardinal = $FF000000; + clipOpacity: cardinal = $FF000000; + +{$R *.dfm} +{$R polygons.res} + +//------------------------------------------------------------------------------ + +function AAFloatPoint2AAPoint(const a: TArrayOfArrayOfFloatPoint; + decimals: integer = 0): TPolygons; +var + i,j,decScale: integer; +begin + decScale := round(power(10,decimals)); + setlength(result, length(a)); + for i := 0 to high(a) do + begin + setlength(result[i], length(a[i])); + for j := 0 to high(a[i]) do + begin + result[i][j].X := round(a[i][j].X *decScale); + result[i][j].Y := round(a[i][j].Y *decScale); + end; + end; +end; +//------------------------------------------------------------------------------ + +function AAPoint2AAFloatPoint(const a: TPolygons; + decimals: integer = 0): TArrayOfArrayOfFloatPoint; +var + i,j,decScale: integer; +begin + decScale := round(power(10,decimals)); + setlength(result, length(a)); + for i := 0 to high(a) do + begin + setlength(result[i], length(a[i])); + for j := 0 to high(a[i]) do + begin + result[i][j].X := a[i][j].X /decScale; + result[i][j].Y := a[i][j].Y /decScale; + end; + end; +end; +//------------------------------------------------------------------------------ + +procedure LoadBinaryStreamToArrayOfArrayOfFloatPoint(stream: TStream; + out fpa: TArrayOfArrayOfFloatPoint); +var + i,j: integer; +begin + try + stream.Read(i, sizeof(i)); + setlength(fpa, i); + for i := 0 to i-1 do + begin + stream.Read(j, sizeof(j)); + setlength(fpa[i], j); + for j := 0 to j-1 do + stream.Read(fpa[i][j], sizeof(TFloatPoint)); + end; + except + fpa := nil; + end; +end; + +//------------------------------------------------------------------------------ +// TMainForm methods +//------------------------------------------------------------------------------ + +procedure TMainForm.FormCreate(Sender: TObject); +begin + tbSubjOpacity.Position := 156; + tbClipOpacity.Position := 156; + Randomize; + StatusBar1.SimpleText := + ' Use the mouse wheel (or +,- & 0) to adjust the clipped region''s offset.'; + ImgView321.Bitmap.Font.Style := [fsBold]; +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.bExitClick(Sender: TObject); +begin + close; +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.ImgView321Resize(Sender: TObject); +begin + ImgView321.SetupBitmap(true, clWhite32); +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.RepaintBitmapI; +var + pfm: TPolyFillMode; + sol: TArrayOfArrayOfFloatPoint; + solI: TPolygons; + scaling: single; +begin + ImgView321.Bitmap.Clear(clWhite32); + + if rbEvenOdd.Checked then pfm := pfAlternate else pfm := pfWinding; + PolyPolygonFS(ImgView321.Bitmap, subj, subjBrushColor or subjOpacity, pfm); + PolyPolylineFS(ImgView321.Bitmap, subj, subjPenColor or subjOpacity, true); + PolyPolygonFS(ImgView321.Bitmap, clip, clipBrushColor or clipOpacity, pfm); + PolyPolylineFS(ImgView321.Bitmap, clip, clipPenColor or clipOpacity, true); + if assigned(solutionI) and not rbNone.Checked then + begin + if offsetMul2 = 0 then + begin + sol := AAPoint2AAFloatPoint(solutionI, scale); + end else + begin + sol := AAPoint2AAFloatPoint(solutionI, scale); + PolyPolylineFS(ImgView321.Bitmap, sol, clGray32, true); + scaling := power(10, scale); + solI := OffsetPolygons(solutionI, offsetMul2/2 *scaling, jtRound); + sol := AAPoint2AAFloatPoint(solI, scale); + end; + PolyPolygonFS(ImgView321.Bitmap, sol, solBrushColor); + + //now add a 3D effect to the solution to make it stand out ... + Simple3D(ImgView321.Bitmap, sol, 3, 3, MAXIMUM_SHADOW_FADE, clWhite32, clBlack32); + PolyPolylineFS(ImgView321.Bitmap, sol, solPenColor, true); + end; + with ImgView321.Bitmap do + begin + Textout(10, height-20, format('Offset = %1.1n pixels',[offsetMul2/2])); + end; + ImgView321.Repaint; +end; +//------------------------------------------------------------------------------ + +function TMainForm.GetFillTypeI: TPolyFillType; +begin + if rbEvenOdd.checked then + result := pftEvenOdd else + result := pftNonZero; +end; +//------------------------------------------------------------------------------ + +function TMainForm.GetOpTypeI: TClipType; +begin + if rbIntersection.Checked then result := ctIntersection + else if rbUnion.Checked then result := ctUnion + else if rbDifference.Checked then result := ctDifference + else result := ctXor; +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.tbSubjOpacityChange(Sender: TObject); +begin + lblSubjOpacity.Caption := format('Subj &Opacity (%d):',[tbSubjOpacity.Position]); + subjOpacity := cardinal(tbSubjOpacity.Position) shl 24; + RePaintBitmapI; +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.tbClipOpacityChange(Sender: TObject); +begin + lblClipOpacity.Caption := format('Clip &Opacity (%d):',[tbClipOpacity.Position]); + clipOpacity := cardinal(tbClipOpacity.Position) shl 24; + RePaintBitmapI; +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.rbStaticClick(Sender: TObject); +begin + if rbStatic.Checked then + ShowStaticPolys + else if rbRandom1.Checked then + ShowRandomPolys1(true) + else + ShowRandomPolys2(true); + + rbNonZero.Enabled := not rbStatic.Checked; + rbEvenOdd.Enabled := not rbStatic.Checked; + lblSubjCount.Enabled := rbRandom1.Checked; + tbSubj.Enabled := rbRandom1.Checked; + tbClip.Enabled := not rbStatic.Checked; + lblClipCount.Enabled := not rbStatic.Checked; +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.rbIntersectionClick(Sender: TObject); +begin + if rbStatic.Checked then ShowStaticPolys + else if rbRandom1.Checked then ShowRandomPolys1(false) + else ShowRandomPolys2(false); +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.bNextClick(Sender: TObject); +begin + if not bNext.Enabled then exit; + if rbRandom1.Checked then ShowRandomPolys1(true) + else ShowRandomPolys2(true); +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.FormResize(Sender: TObject); +begin + if visible then rbIntersectionClick(nil); +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.tbSubjChange(Sender: TObject); +begin + lblSubjCount.Caption := format('Random Subj Count (%d):',[tbSubj.Position]); + lblClipCount.Caption := format('Random Clip Count (%d):',[tbClip.Position]); + if not bNext.Enabled then exit; + //only update random polygons once the mouse has been released ... + if (GetAsyncKeyState(VK_LBUTTON) < 0) then exit; + if rbRandom1.Checked then ShowRandomPolys1(true) + else ShowRandomPolys2(true); +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.rbEvenOddClick(Sender: TObject); +begin + if rbRandom1.Checked then ShowRandomPolys1(false) + else ShowRandomPolys2(false); +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.ShowStaticPolys; +var + rs: TResourceStream; +begin + solution := nil; + rs := TResourceStream.Create(HInstance, 'POLYGON', RT_RCDATA); + LoadBinaryStreamToArrayOfArrayOfFloatPoint(rs, subj); + rs.Free; + + rs := TResourceStream.Create(HInstance, 'CLIP', RT_RCDATA); + LoadBinaryStreamToArrayOfArrayOfFloatPoint(rs, clip); + rs.Free; + + subjI := AAFloatPoint2AAPoint(subj, scale); + clipI := AAFloatPoint2AAPoint(clip, scale); + + if not rbNone.Checked then + with TClipper.Create do + try + AddPolygons(subjI, ptSubject); + AddPolygons(clipI, ptClip); + Execute(GetOpTypeI, solutionI, pftNonZero, pftNonZero); + finally + free; + end; + RepaintBitmapI; +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.ShowRandomPolys1(newPoly: boolean); +var + i,highI,w,h: integer; + fillType: TPolyFillType; +begin + w := (ImgView321.ClientWidth -30); + h := (ImgView321.ClientHeight -30); + fillType := GetFillTypeI; + + if newPoly then + begin + solution := nil; + //nb: although for this demo I chose to display just one random subject + //and one random clip polygon, it would be very easy to make multiple + //subject and clip polygons here. Clipper would handle them just as easily + //(as is demonstrated in ShowStaticPolys). + setLength(subj, 1); + highI := tbSubj.Position -1; + setLength(subj[0], highI+1); + for i := 0 to highI do + subj[0][i] := FloatPoint(10+round(random*w), 10+round(random*h)); + setLength(clip, 1); + highI := tbClip.Position - 1; + setLength(clip[0], highI+1); + for i := 0 to highI do + clip[0][i] := FloatPoint(10+round(random*w), 10+round(random*h)); + end; + + subjI := AAFloatPoint2AAPoint(subj, scale); + clipI := AAFloatPoint2AAPoint(clip, scale); + + if not rbNone.Checked then + with TClipper.Create do + try + AddPolygons(subjI, ptSubject); + AddPolygons(clipI, ptClip); + Execute(GetOpTypeI, solutionI, fillType, fillType); + finally + free; + end; + RepaintBitmapI; +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.ShowRandomPolys2(newPoly: boolean); +var + i,j,w,h: integer; + pt: TFloatPoint; + rec: TFloatRect; + fillType: TPolyFillType; + rs: TResourceStream; +begin + + w := (ImgView321.ClientWidth -30); + h := (ImgView321.ClientHeight -30); + fillType := GetFillTypeI; + + if newPoly then + begin + solution := nil; + + rs := TResourceStream.Create(HInstance, 'AUSTRALIA', RT_RCDATA); + LoadBinaryStreamToArrayOfArrayOfFloatPoint(rs, subj); + rs.Free; + + //make bubbles for clip ... + setlength(clip, tbClip.Position); + for i := 0 to high(clip) do + begin + pt := FloatPoint(random*(w-100) +50, random*(h-100) +50); + j := round(random*45) + 5; + rec := FloatRect(pt.X -j, pt.Y - j, pt.X +j, pt.Y + j); + clip[i] := GetEllipsePoints(rec); + end; + end; + + subjI := AAFloatPoint2AAPoint(subj, scale); + clipI := AAFloatPoint2AAPoint(clip, scale); + + if not rbNone.Checked then + with TClipper.Create do + try + AddPolygons(subjI, ptSubject); + AddPolygons(clipI, ptClip); + Execute(GetOpTypeI, solutionI, fillType, fillType); + finally + free; + end; + RepaintBitmapI; +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.FormMouseWheel(Sender: TObject; Shift: TShiftState; + WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean); +begin + if WheelDelta > 0 then + begin + if offsetMul2 = 20 then exit; + inc(offsetMul2); + RePaintBitmapI; + end + else if WheelDelta < 0 then + begin + if offsetMul2 = -20 then exit; + dec(offsetMul2); + RePaintBitmapI; + end; +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.FormKeyPress(Sender: TObject; var Key: Char); +begin + case Key of + '0',')': offsetMul2 := 0; + '=','+': if offsetMul2 = 20 then exit else inc(offsetMul2); + '-','_': if offsetMul2 = -20 then exit else dec(offsetMul2); + else exit; + end; + RePaintBitmapI; +end; +//------------------------------------------------------------------------------ + +function MakeArrayOfIntPoint(const pts: array of integer): TPolygon; +var + i, len: integer; +begin + result := nil; + len := length(pts) div 2; + if len < 1 then exit; + setlength(result, len); + for i := 0 to len -1 do + begin + result[i].X := pts[i*2]; + result[i].Y := pts[i*2 +1]; + end; +end; +//------------------------------------------------------------------------------ + +procedure TMainForm.bSaveSvgClick(Sender: TObject); +var + invScale: single; +begin + if not SaveDialog1.Execute then exit; + invScale := 1/ power(10, scale); + with TSvgBuilder.Create do + try + style.penWidth := 0.8; + + style.brushClr := $0F0000FF; + style.penClr := $800099FF; + AddPolygons(subjI); + + style.brushClr := $0FFFFF00; + style.penClr := $80FF9900; + AddPolygons(clipI); + + style.brushClr := $2000FF00; + style.penClr := $FF006600; + AddPolygons(solutionI); + + SaveToFile(SaveDialog1.FileName, invScale); + finally + free; + end; +end; +//------------------------------------------------------------------------------ + +end. diff --git a/clipper/Delphi/main demo/polygons.res b/clipper/Delphi/main demo/polygons.res new file mode 100755 index 0000000..272463a Binary files /dev/null and b/clipper/Delphi/main demo/polygons.res differ diff --git a/clipper/Documentation/Docs/Overview/Changes.htm b/clipper/Documentation/Docs/Overview/Changes.htm new file mode 100755 index 0000000..99d3246 --- /dev/null +++ b/clipper/Documentation/Docs/Overview/Changes.htm @@ -0,0 +1,793 @@ + + + + + + + + + + + + + + + + + + + + + Changes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Changes

+ + + + + +

See Also

+

Compatibility with Prior Versions, Rounding, Clipper.Execute, PolyNode, PolyTree, Area, CleanPolygon, CleanPolygons, OffsetPolygons, Orientation, SimplifyPolygon, SimplifyPolygons, IntPoint, PolyFillType, Polygons

+ + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Overview/Compatibility with Prior Versions.htm b/clipper/Documentation/Docs/Overview/Compatibility with Prior Versions.htm new file mode 100755 index 0000000..acf202a --- /dev/null +++ b/clipper/Documentation/Docs/Overview/Compatibility with Prior Versions.htm @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + Compatibility with Prior Versions + + + + + + + + + + + + + + + + + + + + + +

Compatibility with Prior Versions

+ + +

+

ExPolygons

+ +

In Clipper Ver 5.1, the ExPolygons structure was replaced with the PolyTree class.

The PolyTreeToExPolygons() function below and its accompanying code may be useful if for some reason you are stuck with using ExPolygons.

+ + + + + + + +
Delphi ... +
+ +
+
+  type
+    TExPolygon = record
+      Outer: TPolygon;
+      Holes: TPolygons;
+    end;
+    TExPolygons = array of TExPolygon;
+
+  procedure AddOuterPolyNodeToExPolygons(PolyNode: TPolyNode; 
+    var ExPolygons: TExPolygons);
+  var
+    I, J, Cnt: Integer;
+  begin
+    Cnt := Length(ExPolygons);
+    SetLength(ExPolygons, Cnt + 1);
+    ExPolygons[Cnt].Outer := PolyNode.Contour;
+    SetLength(ExPolygons[Cnt].Holes, PolyNode.ChildCount);
+    for I := 0 to PolyNode.ChildCount - 1 do
+    begin
+      ExPolygons[Cnt].Holes[I] := PolyNode.Childs[I].Contour;
+      //Add outer polygons contained by (nested within) holes ...
+      for J := 0 to PolyNode.Childs[I].ChildCount - 1 do
+        AddOuterPolyNodeToExPolygons(PolyNode.Childs[I].Childs[J], ExPolygons);
+    end;
+  end;
+
+  function PolyTreeToExPolygons(PolyTree: TPolyTree): TExPolygons;
+  var
+    I: Integer;
+  begin
+    Result := nil;
+    for I := 0 to PolyTree.ChildCount - 1 do
+      AddOuterPolyNodeToExPolygons(PolyTree.Childs[I], Result);
+  end;
+          
+ +


+ + + + + + + +
C++ ... +
+ +
+
+   struct ExPolygon {
+    Polygon outer;
+    Polygons holes;
+  };
+
+  typedef std::vector< ExPolygon > ExPolygons;
+
+  void AddOuterPolyNodeToExPolygons(PolyNode& polynode, ExPolygons& expolygons)
+  {  
+    size_t cnt = expolygons.size();
+    expolygons.resize(cnt + 1);
+    expolygons[cnt].outer = polynode.Contour;
+    expolygons[cnt].holes.resize(polynode.ChildCount());
+    for (int i = 0; i < polynode.ChildCount(); ++i)
+    {
+      expolygons[cnt].holes[i] = polynode.Childs[i]->Contour;
+      //Add outer polygons contained by (nested within) holes ...
+      for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++j)
+        AddOuterPolyNodeToExPolygons(*polynode.Childs[i]->Childs[j], expolygons);
+    }
+  }
+
+  void PolyTreeToExPolygons(PolyTree& polytree, ExPolygons& expolygons)
+  {
+    expolygons.clear();
+    for (int i = 0; i < polytree.ChildCount(); ++i)
+      AddOuterPolyNodeToExPolygons(*polytree.Childs[i], expolygons);
+  }
+          
+ +


+ + + + + + + +
C# ... +
+ +
+
+  using ExPolygons = List<ExPolygon>;	
+  using Polygon = List<IntPoint>;
+  using Polygons = List<List<IntPoint>>;
+
+  public struct ExPolygon {
+      public Polygon outer;
+      public Polygons holes;
+  }    
+
+  void AddOuterPolyNodeToExPolygons(PolyNode polynode, ref ExPolygons expolygons)
+  {  
+    ExPolygon ep = new ExPolygon();
+    ep.outer = new Polygon(polynode.Contour);
+    ep.holes = new Polygons(polynode.ChildCount);
+    foreach (PolyNode node in polynode.Childs)
+    {
+      ep.holes.Add(node.Contour);
+      //Add outer polygons contained by (nested within) holes ...
+      foreach (PolyNode n in node.Childs)
+          AddOuterPolyNodeToExPolygons(n, ref expolygons);
+    }
+    expolygons.Add(ep);
+  }
+
+  void PolyTreeToExPolygons(PolyTree polytree, ref ExPolygons expolygons)
+  {
+      expolygons.Clear();
+      foreach (PolyNode node in polytree.Childs)
+        AddOuterPolyNodeToExPolygons(node, ref expolygons);
+  }
+          
+ +


+
 
+ +

See Also

+

PolyTree

+ + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Overview/Example.htm b/clipper/Documentation/Docs/Overview/Example.htm new file mode 100755 index 0000000..4f59a2c --- /dev/null +++ b/clipper/Documentation/Docs/Overview/Example.htm @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + Example + + + + + + + + + + + + + + + + + + + + + +

Example

+ + + + + + + + + +
Delphi Code Sample: +
+ +
+  uses
+    graphics32, clipper;
+  
+  ...
+  	
+  var
+    sub, clp, sol: TPolygons;
+  begin
+
+    //set up the subject and clip polygons ...
+    setlength(sub, 3);
+    sub[0] := GetEllipsePoints(IntRect(100,100,300,300));
+    sub[1] := GetEllipsePoints(IntRect(125,130,275,180));
+    sub[2] := GetEllipsePoints(IntRect(125,220,275,270));
+	
+    setlength(clp, 1);
+    clp[0] := GetEllipsePoints(IntRect(140,70,220,320));
+
+    //display the subject and clip polygons ...
+    DrawPolygons(img.Bitmap, sub, 0x8033FFFF);
+    DrawPolygons(img.Bitmap, clp, 0x80FFFF33);
+    
+    //get the intersection of the subject and clip polygons ...
+    with TClipper.Create do
+    try
+      AddPolygons(sub, ptSubject);
+      AddPolygons(clp, ptClip);
+      Execute(ctIntersection, sol, pftEvenOdd, pftEvenOdd);
+    finally
+      free;
+    end;
+    
+    //finally draw the intersection polygons ...
+    DrawPolygons(img.Bitmap, sol, 0x40808080);
+        
+ +
+
 
+ + + + + + + + + + +
C++ Code Sample: +
+ +
+  #include "clipper.hpp"
+  
+  ...
+
+  //from clipper.hpp ...
+  //typedef long long long64;
+  //struct IntPoint {long64 X; long64 Y;};
+  //typedef std::vector<IntPoint> Polygon;
+  //typedef std::vector<Polygon> Polygons;
+
+  using namespace ClipperLib;
+
+
+  //set up the subject and clip polygons ...
+  Polygons sub(3);
+  sub[0] = GetEllipsePoints(IntRect(100,100,300,300));
+  sub[1] = GetEllipsePoints(IntRect(125,130,275,180));
+  sub[2] = GetEllipsePoints(IntRect(125,220,275,270));
+
+  Polygons clp(1);
+  clp[0] = GetEllipsePoints(IntRect(140,70,220,320));
+
+  //display the subject and clip polygons ...
+  DrawPolygons(img->Bitmap, sub, 0x8033FFFF);
+  DrawPolygons(img->Bitmap, clp, 0x80FFFF33);
+  
+  //get the intersection of the subject and clip polygons ...
+  Clipper clpr;
+  clpr.AddPolygons(sub, ptSubject);
+  clpr.AddPolygons(clp, ptClip);
+  Polygons sol;
+  clpr.Execute(ctIntersection, sol, pftEvenOdd, pftEvenOdd);
+
+  //finally draw the intersection polygons ...
+  DrawPolygons(img->Bitmap, sol, 0x40808080);
+        
+ +
+
 
+ + + + + + + + + +
C# Code Sample: +
+ +
+  ...
+  using ClipperLib;
+	
+  ...
+  using Polygon = List<IntPoint>;
+  using Polygons = List<List<IntPoint>>;
+  
+  ...
+  
+  Polygons subjs = new Polygons(3);
+  Polygons clips = new Polygons(1);
+  Polygons solution = new Polygons();
+	
+  subjs.Add(GetEllipsePoints(new IntRect(100,100,300,300)));
+  subjs.Add(GetEllipsePoints(new IntRect(125,130,275,180)));
+  subjs.Add(GetEllipsePoints(new IntRect(125,220,275,270)));
+	  
+  clips.Add(GetEllipsePoints(new IntRect(140,70,220,320)));
+	  
+  DrawPolygons(subjs, 0x8033FFFF);
+  DrawPolygons(clips, 0x80FFFF33);
+	  
+  Clipper c = new Clipper();
+  c.AddPolygons(subjs, PolyType.ptSubject);
+  c.AddPolygons(clips, PolyType.ptClip);
+  c.Execute(ClipType.ctIntersection, solution);
+
+  DrawPolygons(solution, 0x40808080);
+        
+ +
+ +
 
+ + + + + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Overview/FAQ.htm b/clipper/Documentation/Docs/Overview/FAQ.htm new file mode 100755 index 0000000..59ec686 --- /dev/null +++ b/clipper/Documentation/Docs/Overview/FAQ.htm @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + FAQ + + + + + + + + + + + + + + + + + + + + + +

FAQ

+ + +

Why does Clipper use integer coordinates, not floats?

+ +

Initially Clipper did use floating point coordinates but the clipping algorithm wasn't numerically robust. Consequently, very occasionally errors occurred which prevented Clipper from returning a solution. These issues have completely resolved since swapping to integer coordinates.

+ + +

How do I use floating point coordinates with Clipper?

+ +

It's a simple task to multiply your floating point coordinates by a scaling factor (that's typically a power of 10 depending on the desired precision). Then with the solution polygons, divide the returned coordinates by this same scaling factor. Clipper accepts integer coordinates as large as +/-4.6 e18, so it can accommodate very large scaling factors.

+ + +

Does Clipper handle polygons with holes?

+ +

Polygon 'holes' are implied simply by having their orientations opposite that of their container polygons.

+ + +

Why are there separate fill rules for Subject and Clip polygons?

+ +

Because users may want to use different fill rules for these polygons. However it's important to note that these fill rules apply solely within subject and clip polygons to define their respective regions. These rules aren't applied to the clip operation itself where subject and clip polygons are merged into a single solution. If you wish for a fill rule to apply to the merging of subject and clip polygons as well - typically during a UNION operation, then you should UNION all the polygons as subjects without assigning any clip polygons.

+ + +

Which fill rule does the boolean clipping operation use?

+ +

Perhaps the easiest way to explain what happens during clipping is to consider the following. First the subject and clip polygons are filled using their respective fill rules. With the subject polygons, assign a winding count (WC) of +1 to any filled region and a WC of 0 to any unfilled region. Do likewise with the clip polygons. Then the following winding rules apply to the clipping operation ...
  ♦ Intersection: add subject and clip regions and return those regions where WC == +2
  ♦ Union: add subject and clip regions and return those regions where WC > 0
  ♦ Difference: subtract clip from subject regions and return those regions where WC == +1
  ♦ XOR: subtract clip from subject regions and return those regions where WC != 0

+ + +

Some solution polygons share a common edge. Is this a bug?

+ +

No. However Clipper tries very hard to minimize this by merging polygons that share a common edge.

+ + +

I have lots of polygons that I want to 'union'. Can I do this in one operation?

+ +

Yes. Just add all the polygons as subject polygons to the Clipper object and don't assign any clip polygons.

+ + +

The OffsetPolygons function is returning tiny artefacts? Could this be a bug?

+ +

The precision of the input coordinates may be a problem. The Clipper Library only operates on integer coordinates so if you need better precision than integers, scale the coordinates (eg by a factor of 10) before passing them to the OffsetPolygons function. Then it's a simple matter to reverse the scaling on the output polygons.

+ + +

The OffsetPolygons function is returning unexpected results? Could this be a bug?

+ +

Most likely the orientation of the input polygons is wrong.

+ + +

Is there an easy way to reverse polygon orientations?

+ +

Yes, see ReversePolygons.

+ +
+ + +

Is it possible to get the offset of a line or a polyline?

+ +

Yes, just convert the polyline into a 'flat' polygon. Do this by appending to the polyline a reverse copy of the polyline while avoiding duplicate coordinates at each end: c1,c2,...,cn, c(n-1),...,c2. (You don't need to append anything if there are just two vertices forming a single line.)

+ + + + + + +
+ +
+var
+  pts: TPolygon;
+  ppts: TPolygons;
+begin
+  //define the polyline ...
+  setlength(pts, 5);
+  pts[0] := IntPoint(10,10);
+  pts[1] := IntPoint(100,100);
+  pts[2] := IntPoint(150,100);
+  pts[3] := IntPoint(100,10);
+  pts[4] := IntPoint(10,100);
+
+  //convert the line to a 'flat' polygon ...
+  len := length(pts);
+  setLength(pts, len*2 -2);
+  for i := 1 to len -2 do pts[len-1 +i] := pts[len-1 -i];
+
+  //do the offsetting ...
+  setlength(ppts, 1);
+  ppts[0] := pts;
+  ppts := OffsetPolygons(ppts, 6, jtSquare, 0);
+          
+ +


+ +
 
+ + +

My drawings contain lots of ellipses and arcs. How can I perform clipping operations or offsetting on these?

+ +

You'll have to convert then to polygons. Many graphics libraries have 'flatten path' routines.

+ + + +

See Also

+

OffsetPolygons, Orientation, ReversePolygons

+ + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Overview/License.htm b/clipper/Documentation/Docs/Overview/License.htm new file mode 100755 index 0000000..0a3dddb --- /dev/null +++ b/clipper/Documentation/Docs/Overview/License.htm @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + License + + + + + + + + + + + + + + + + + + + + + +

License

+ + +

The Clipper code library, the "Software" (that includes Delphi, C++ & C# source code, accompanying samples and documentation), has been released under the following license, terms and conditions:

+ + +

Boost Software License - Version 1.0 - August 17th, 2003
http://www.boost.org/LICENSE_1_0.txt

+ + +

Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following:

+ + +

The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor.

+ + +

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ + + + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Overview/Rounding.htm b/clipper/Documentation/Docs/Overview/Rounding.htm new file mode 100755 index 0000000..8390a43 --- /dev/null +++ b/clipper/Documentation/Docs/Overview/Rounding.htm @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + Rounding + + + + + + + + + + + + + + + + + + + + + +

Rounding

+ +

By requiring integer coordinates for all polygon vertices, the Clipper Library has been able to avoid problems of numerical robustness that otherwise plague geometric computations. Nevertheless, rounding coordinates to integers causes other problems, and these are discussed below.
+



It is important to stress at the outset that rounding causes some unavoidable imprecision by moving vertices fractions of a unit away from their 'true' positions. Fortunately rounding imprecision can be managed effectively by appropriate scaling.


Nevertheless inappropriate scaling can deliver undesirable and perhaps unexpected solutons as demonstrated by the first example on the right. The image shows two polygons (triangles) being merged with a 'union' operation. The region shaded green represents the 'merged' polygon returned by Clipper. It's evident that the bottom-left polygon from the input is missing from the result.

This is perhaps best explained by a very simple overview of how the clipping algorithm is implemented. Imaginary horizontal lines (called scanlines) pass through each and every vertex in the supplied set of polygons (ie both subject and clip polygons). The regions between adjacent scanlines are called scanbeams. Scanbeams are processed in order, starting with the bottom-most scanbeam and proceeding to the top-most. For each scanbeam there is a set of 'active' edges, that is those edges that pass through that scanbeam. The relative positions of active edges at both the bottom and top of a given scanbeam are used to determine the locations of intersections within a scanbeam. To preserve numerical robustness it's necessary to use the rounded coordinates of each edge at each scanline. This rounding effectively causes edges to deviate fractions of a unit horizontally where they cross each scanline.

In the image on the right the edge (2,5) -> (1,3) deviates from its true position at (1.5,4) to (2,4) at scanline Y=4. This edge deviation reduces the bottom-left polygon's area to zero and as a consequence it is discarded.

If the polygon coordinates of these 2 triangles had been scaled up by even just a factor of 2, ie {(2,6), (4,8), (4,10)} and {(2,6), (6,6), (4,8)}, the union operation would have returned a polygon that correctly covers both triangles.

Greater precision can always be achieved by scaling (or increased scaling) of polygon coordinates. The Clipper library accepts integer coordinate values up to ±0x3FFFFFFFFFFFFFFF (± 4.6e+18) in order to accommodate very high degrees of precision.



This second clipping example shows another complication of rounding - tiny self-intersection artefacts. Even though polygon vertices passed to Clipper objects have integer coordinates, their edges commonly intesect with other edges at fractional coordinates. These intersection points (that form vertices in the solution polygons) must have their coordinates rounded to integers.

In the unscaled image on the left (where one unit equals one pixel), the area of intersection of 2 polygons has been highlighted in bright green.

+ +



A 30X 'close up' of the lower points of intersection of these same 2 polygons shows the presence of a tiny self-intersecting artefact. The three 'black dots' highlight the actual points of intersection (with their fractional coordinates displayed). The smaller 'red dots' show where these points of intersection are located once rounding is applied. With a little care you can see that rounding reverses the orientation of these vertices and causes a tiny self-intersecting artefact.

+ + +



In this final example, the single polygon on the left also has a tiny self-intersection. However, due to the way Clipper uses rounding (as very briefly outlined above), the vertex (88,50) is seen to simply 'touch' rather than cross over (by a fraction of a unit) the right edge of the polygon. Consequently a union operation by Clipper on this polygon will return this same polygon unchanged (ie without removing the tiny self-intersection).

+ + + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Overview/_Body.htm b/clipper/Documentation/Docs/Overview/_Body.htm new file mode 100755 index 0000000..28a4922 --- /dev/null +++ b/clipper/Documentation/Docs/Overview/_Body.htm @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + Overview + + + + + + + + + + + + + + + + + + + + + + +

Overview

+ + +

The Clipper Library is based on but significantly extends Bala Vatti's polygon clipping algorithm as described in "A generic solution to polygon clipping", Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63.

The library can perform each of the four boolean clipping operations - intersection, union, difference and exclusive-or (xor). It can accept any number of, and any type of polygon as input (including polygons with holes and self-intersecting polygons).

+ Clipper's ZIP package contains the core library, a Windows CHM help file, HTML help, and a number of compiled examples. The library's code was initially written in Delphi Pascal (compiling in Delphi ver. 7+) but now contains C++ and C# translations too. The core library's source code in each language is a little over 3500 lines (including extensive comments), with a file size of about 100KB. The examples show how Clipper can be used with the different languages using a number of graphics libraries including - AGG, Cairo, OpenGL, Graphics32 and GDI+.

Several features set Clipper apart from other polygon clipping libraries:

    + +
  1. it's very fast compared to other libraries
  2. + +
  3. the ability to handle self-intersecting polygons
  4. + +
  5. it's free, under its Boost license, to use in both freeware and commercial applications.
  6. + +
  7. support for EvenOdd, NonZero, Positive and Negative polygon filling modes.
  8. + +
  9. ability to perform polygon offsetting.
  10. + +

+
+ + +

+ + + +

See Also

+

Source, License, Clipper

+ + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/Clipper/Methods/Constructor.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/Clipper/Methods/Constructor.htm new file mode 100755 index 0000000..fc50981 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/Clipper/Methods/Constructor.htm @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + Constructor + + + + + + + + + + + + + + + + + + + + + + +

Clipper.Constructor

+ + +

Del.» constructor Create; override;

+ +

C++ » Clipper();

+ +

C#  » Clipper();

+ + + + + +

See Also

+

Example

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/Clipper/Methods/Execute.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/Clipper/Methods/Execute.htm new file mode 100755 index 0000000..4c96940 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/Clipper/Methods/Execute.htm @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + Execute + + + + + + + + + + + + + + + + + + + + + + +

Clipper.Execute

+ + +

Del.»
function Execute(clipType: TClipType;
  out solution: TPolygons;
  subjFillType: TPolyFillType = pftEvenOdd;
  clipFillType: TPolyFillType = pftEvenOdd): boolean; overload;

function Execute(clipType: TClipType;
  out solution: TPolyTree;
  subjFillType: TPolyFillType = pftEvenOdd;
  clipFillType: TPolyFillType = pftEvenOdd): boolean; overload;

+ + +

C++ »
bool Execute(ClipType clipType,
  Polygons &solution,
  PolyFillType subjFillType = pftEvenOdd,
  PolyFillType clipFillType = pftEvenOdd);

bool Execute(ClipType clipType,
  PolyTree &solution,
  PolyFillType subjFillType = pftEvenOdd,
  PolyFillType clipFillType = pftEvenOdd);

+ +

C#  »
public bool Execute(ClipType clipType,
  Polygons solution,
  PolyFillType subjFillType,
  PolyFillType clipFillType);

public bool Execute(ClipType clipType,
  PolyTree solution,
  PolyFillType subjFillType,
  PolyFillType clipFillType);

+ +
+ +

The Execute() method performs the specified clipping task (intersection, union, difference or xor) on the previously assigned subject and clip polygons. This method can be called multiple times without reassigning subject and clip polygons (ie when different clipping operations are required on the same polygon sets). The polygon fill methods can be Even-Odd, Non-Zero, Positive and Negative (default: Even-Odd). Also, subject and clip polygons don't have to use the same fill type.

This method is overloaded since the solution parameter can be either a Polygons or PolyTree structure. When the solution parameter is a Polygons structure, the solution will contain a collection of polygons containing both outer and inner polygon contours (where inner contours have an orientation opposite outer contours). On the other hand, the PolyTree structure explicitly associates inner 'hole' polygons with their container 'outer' polygons.

The order that polygons are listed in the solution structure is undefined. The solution will never contain overlapping polygons, nor will it contain polygons with self-intersecting edges. Also, the filling mode for solution polygons is undefined since it can be either EvenOdd or NonZero. (This is because the winding depth of solution polygons will always be in the range of -1 to 1.)

Solution orientation: The Orientation of polygons returned in the 'solution' parameter will always be 'true' for outer polygons and 'false' for inner 'hole' polygons (unless the ReverseSolution property has been enabled).

It's important to note that the fill rules apply solely within subject and clip polygons to define their respective regions. These rules aren't applied when merging these polygons into a single solution during the clip operation. If you wish for a fill rule to apply to the merging of subject and clip polygons as well - typically during a UNION operation, then you should UNION all the polygons as subjects without assigning clip polygons.

+ + +

See Also

+

Example, ReverseSolution, PolyTree, Orientation, ClipType, PolyFillType, Polygons

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/Clipper/Properties/ReverseSolution.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/Clipper/Properties/ReverseSolution.htm new file mode 100755 index 0000000..0fa988a --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/Clipper/Properties/ReverseSolution.htm @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + ReverseSolution + + + + + + + + + + + + + + + + + + + + + + +

Clipper.ReverseSolution

+ + +

Del.» property ReverseSolution: boolean; override;

+ +

C++ » void ReverseSolution(bool value);

+ +

C#  » public bool ReverseSolution { get {} set {} };

+ + +

When this property is set to true, polygons returned in the solution parameter of the Execute() method will have orientations opposite to their normal orientations.

+ + + + + +

See Also

+

Execute, Orientation

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/Clipper/_Body.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/Clipper/_Body.htm new file mode 100755 index 0000000..b69f1a3 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/Clipper/_Body.htm @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + Clipper + + + + + + + + + + + + + + + + + + + + + + + +

Clipper

+

Hierarchy

+

+

   |

+

ClipperBase

+
+ +

The Clipper class encapsulates boolean clipping operations (intersection, union, difference and XOR) on polygons.

Input polygons are passed to a Clipper object by the AddPolygon and AddPolygons methods, and the clipping operation is performed by the Execute method. Multiple boolean operations can be performed on the same input polygon set by repeat calls to Execute. However, if a differenct set of input polygons requires 'clipping', then the Clear method must be called to remove any existing polygons before new polygons are added.

+ +

Reference

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Methods + Properties +
In Clipper: +
Constructor + ReverseSolution +
Execute + +
In ClipperBase: +
AddPolygon + +
AddPolygons + +
Clear + +
GetBounds + +
+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/AddPolygon.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/AddPolygon.htm new file mode 100755 index 0000000..14a7969 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/AddPolygon.htm @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + AddPolygon + + + + + + + + + + + + + + + + + + + + + + +

ClipperBase.AddPolygon

+ + +

Del.» function AddPolygon(const polygon: TPolygon; polyType: TPolyType): boolean;

+ +

C++ » bool AddPolygon(const Polygon &pg, PolyType polyType);

+ +

C#  » public virtual bool AddPolygon(Polygon pg, PolyType polyType);

+
+ + +

Any number of subject and clip polygons can be added to the clipping task, either individually via the AddPolygon() method, or as groups via the AddPolygons() method, or even using both methods.

Adjacent vertices with identical coordinates will be treated as a single vertex. Whereever 3 adjacent vertices form a single colinear edge, the middle vertex will be ignored.

+
+ + +

Polygon Orientation:
Outer polygons can be either clockwise or counter-clockwise as long as any inner 'hole' polygons have the reverse orientation.

+
+ + +

Polygon Coordinate range:
Polygon coordinates must be between ± 0x3FFFFFFFFFFFFFFF (± 4.6e+18), otherwise a range error will be thrown when attempting to add the polygon to the Clipper object. If coordinates can be kept between ± 0x3FFFFFFF (± 1.0e+9), a modest increase in performance (approx. 15-20%) over the larger range can be achieved by avoiding large integer math.

+ + +

The function will return false if: +

    + +
  • there are less than 3 coordinates defining the polygon (once duplicates have been excluded).
  • + +
  • all polygon coordinates are co-linear.
  • + +

+ +
+ + + + +

See Also

+

Example, AddPolygons, Polygon, PolyType

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/AddPolygons.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/AddPolygons.htm new file mode 100755 index 0000000..1d240fa --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/AddPolygons.htm @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + AddPolygons + + + + + + + + + + + + + + + + + + + + + + +

ClipperBase.AddPolygons

+ + +

Del.» function AddPolygons(const polygons: TPolygons; polyType: TPolyType): boolean;

+ +

C++ » bool AddPolygons(const Polygons &ppg, PolyType polyType);

+ +

C#  » public virtual bool AddPolygons(Polygons ppg, PolyType polyType);

+
+ +

Any number of subject and clip polygons can be added to the clipping task, either individually via the AddPolygon() method, or as groups via the AddPolygons() method, or even using both methods.

Adjacent vertices with identical coordinates will be treated as a single vertex. Whereever 3 adjacent vertices form a single colinear edge, the middle vertex will be ignored.

+
+ + +

Polygon Orientation:
Outer polygons can be either clockwise or counter-clockwise as long as any inner 'hole' polygons have the reverse orientation.

+
+ + +

Polygon Coordinate range:
Polygon coordinates must be between ± 0x3FFFFFFFFFFFFFFF (± 4.6e+18), otherwise a range error will be thrown when attempting to add the polygon to the Clipper object. If coordinates can be kept between ± 0x3FFFFFFF (± 1.0e+9), a modest increase in performance (approx. 15-20%) over the larger range can be achieved by avoiding large integer math.

+ + +

The function will return false if: +

    + +
  • there are less than 3 coordinates defining the polygon (once duplicates have been excluded).
  • + +
  • all polygon coordinates are co-linear.
  • + +

+ +
+ + + + +

See Also

+

Example, AddPolygon, Polygons, PolyType

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/Clear.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/Clear.htm new file mode 100755 index 0000000..eb2b817 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/Clear.htm @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + Clear + + + + + + + + + + + + + + + + + + + + + + +

ClipperBase.Clear

+ + +

Del.» procedure Clear;

+ +

C++ » virtual void Clear();

+ +

C#  » public void Clear() {};

+ +

The Clear method allows the Clipper object to be reused when clipping operations are required on different polygon sets. (If different clipping operations are performed on the same polygon sets, then Clipper's Execute method can be safely repeated without clearing and re-adding polygons.)

+ + + +

See Also

+

Clipper.Execute

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/GetBounds.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/GetBounds.htm new file mode 100755 index 0000000..183ceaf --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/GetBounds.htm @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + GetBounds + + + + + + + + + + + + + + + + + + + + + + +

ClipperBase.GetBounds

+ + +

Del.» function GetBounds: TIntRect;

+ +

C++ » IntRect GetBounds();

+ +

C#  » public IntRect GetBounds() {...};

+
+ +

This method returns the axis-aligned bounding rectangle of all polygons that have been added to the Clipper object.

+ +
+ + + +

See Also

+

Example, IntRect

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/_Body.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/_Body.htm new file mode 100755 index 0000000..e8344b5 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/ClipperBase/_Body.htm @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + ClipperBase + + + + + + + + + + + + + + + + + + + + + + + +

ClipperBase

+ +

ClipperBase is the ancestor class to Clipper (or TClipper in Delphi). It should not be instantiated directly. This class converts polygon coordinates (passed by the AddPolygon and AddPolygons methods) into edge objects.

+ + +

Reference

+ + + + + + + + + + + + + + + + + + +
Methods +
In ClipperBase: +
AddPolygon +
AddPolygons +
Clear +
GetBounds +
+

See Also

+

Clipper

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Methods/GetNext.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Methods/GetNext.htm new file mode 100755 index 0000000..fab87a7 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Methods/GetNext.htm @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + GetNext + + + + + + + + + + + + + + + + + + + + + + +

PolyNode.GetNext

+ + +

Del.» function GetNext: TPolyNode;

+ +

C++ » PolyNode* GetNext();

+ +

C#  » public PolyNode GetNext();

+ + +

The returned Polynode will be the first child if any, otherwise the next sibling, otherwise the next sibling of the Parent etc.

A PolyTree can be traversed very easily by calling GetFirst() followed by GetNext() in a loop until the returned object is a null pointer ...

+ + + + + + +
+ +
+  PolyTree polytree;
+  //call to Clipper.Execute method here which fills 'polytree'
+  
+  PolyNode* polynode = polytree.GetFirst();
+  while (polynode)
+  {
+    //do stuff with polynode here
+	
+    polynode = polynode->GetNext();
+  }
+  
+          
+ +

+ + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/ChildCount.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/ChildCount.htm new file mode 100755 index 0000000..b915d26 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/ChildCount.htm @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + ChildCount + + + + + + + + + + + + + + + + + + + + + + +

PolyNode.ChildCount

+ + +

Del.» property ChildCount: Integer; //read only

+ +

C++ » ChildCount(); //read only

+ +

C#  » public int ChildCount; //read only

+ +
+ +

Returns the number of PolyNode Childs directly owned by the PolyNode object.

+ + +

See Also

+

Childs

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/Childs.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/Childs.htm new file mode 100755 index 0000000..5955de9 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/Childs.htm @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + Childs + + + + + + + + + + + + + + + + + + + + + + +

PolyNode.Childs

+ + +

Del.» property Childs[index: Integer]: TPolyNode; //read only

+ +

C++ » std::vector < PolyNode* > Childs;//public field

+ +

C#  » public List < PolyNode > Childs; //read only property

+ + +

A read-only list of PolyNode.
Outer PolyNode childs contain hole PolyNodes, and hole PolyNode childs contain nested outer PolyNodes.

+ + + + +

See Also

+

ChildCount

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/Contour.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/Contour.htm new file mode 100755 index 0000000..76097e8 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/Contour.htm @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + Contour + + + + + + + + + + + + + + + + + + + + + + +

PolyNode.Contour

+ +

Del.» property Contour: TPolygon; //read only

+ +

C++ » Polygon Contour; //public field

+ +

C#  » public Polygon Contour; //read only property

+ + +

Returns a list of polygon coordinates.

+ +

See Also

+

Orientation

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/IsHole.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/IsHole.htm new file mode 100755 index 0000000..ab1a21c --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/IsHole.htm @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + IsHole + + + + + + + + + + + + + + + + + + + + + + +

PolyNode.IsHole

+ + +

Del.» IsHole: Boolean; //read only

+ +

C++ » bool IsHole; //field

+ +

C#  » public bool IsHole; //read only property

+ + +

Returns true when the PolyNode's polygon (Contour) is a hole.

+ +

See Also

+

Contour

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/Parent.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/Parent.htm new file mode 100755 index 0000000..97359b8 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/Properties/Parent.htm @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + Parent + + + + + + + + + + + + + + + + + + + + + + +

PolyNode.Parent

+ +

Del.» Parent: TPolyNode; //read only

+ +

C++ » PolyNode* Parent; //field

+ +

C#  » public PolyNode Parent; //read only property

+ + +

Returns the parent PolyNode.

The PolyTree object (which is also a PolyNode) does not have a parent and will return a null pointer.

+ + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/_Body.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/_Body.htm new file mode 100755 index 0000000..ca9ef2c --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyNode/_Body.htm @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + PolyNode + + + + + + + + + + + + + + + + + + + + + + + +

PolyNode

+
+ +

PolyNodes are encapsulated within a PolyTree container, and together provide a data structure representing the parent-child relationships of polygon contours returned by Clipper's Execute method.

PolyNode objects represent single polygon contours that can be either 'outer' or a 'hole' polygons. PolyNodes may own any number of PolyNode children (Childs), where children of outer polygons are holes, and children of holes are (nested) outer polygons.

+ +

Reference

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fields + Methods + Properties +
In PolyNode: +
+ GetNext + ChildCount +
+ + Childs +
+ + Contour +
+ + IsHole +
+ + Parent +
+

See Also

+

Clipper.Execute, PolyTree

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyTree/Methods/Clear.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyTree/Methods/Clear.htm new file mode 100755 index 0000000..89a85fa --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyTree/Methods/Clear.htm @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + Clear + + + + + + + + + + + + + + + + + + + + + + +

PolyTree.Clear

+ + +

Del.» procedure Clear;

+ +

C++ » void Clear();

+ +

C#  » public void Clear();

+
+ + +

This method allows a PolyTree object to be reused in repeat calls to Clipper's Execute method.

Note: This method does not need to be called explicitly between calls to Clipper.Execute() since that method will automatically clear the PolyTree before propogating it with new PolyNodes.

+ +

See Also

+

Clipper.Execute

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyTree/Methods/GetFirst.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyTree/Methods/GetFirst.htm new file mode 100755 index 0000000..2ea3340 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyTree/Methods/GetFirst.htm @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + GetFirst + + + + + + + + + + + + + + + + + + + + + + +

PolyTree.GetFirst

+ + +

Del.» function GetFirst: TPolyNode;

+ +

C++ » PolyNode* GetFirst();

+ +

C#  » public PolyNode GetFirst();

+ + +

Ths method returns the first outer polygon contour if any, otherwise a null pointer.

This function is almost equivalent to calling Childs[0] except that when a PolyTree object is empty (has no children), calling Childs[0] would raise an out of range exception.

+ + + + + + +

See Also

+

PolyNode.GetNext, PolyNode.ChildCount, PolyNode.Childs

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyTree/Properties/Total.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyTree/Properties/Total.htm new file mode 100755 index 0000000..62a4854 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyTree/Properties/Total.htm @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + Total + + + + + + + + + + + + + + + + + + + + + + +

PolyTree.Total

+ + +

Del.» property Total: Integer; //read only

+ +

C++ » Total(); //read only

+ +

C#  » public int Total; //read only

+ +
+ +

Returns the total number of PolyNodes (polygons) contained within the PolyTree. This value is not to be confused with ChildCount which returns the number of immediate children only (Childs) contained by PolyTree.

+ + +

See Also

+

PolyNode.ChildCount, PolyNode.Childs

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyTree/_Body.htm b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyTree/_Body.htm new file mode 100755 index 0000000..ad896a8 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Classes/PolyTree/_Body.htm @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + PolyTree + + + + + + + + + + + + + + + + + + + + + + + +

PolyTree

+

Hierarchy

+

+

   |

+

PolyNode

+
+ +

A PolyTree object can be passed as a parameter to Clipper's Execute method. This class encapsulates a data structure that mirrors the parent-child relationships of the polygons returned by Clipper. (The PolyTree class replaces the obsolete ExPolygons structure that was unable to represent polygons 'nesting' within holes.)

The term 'polygon' is often used by others to refer to a single 'outer' contour together with any number of inner contours or 'holes'. However, in this documentation, polygon is used synonymously with a single polygon contour, irrespective of whether it's an outer or a hole.

The PolyTree object encapsulates any number PolyNode children, where a PolyNode object represents a single polygon (either an outer or hole polygon). PolyTree is a specialized PolyNode that contains all the PolyNodes within the tree. Its Contour property will always be empty. The PolyTree's immediate children represent top-level 'outer' polygons returned by the Clipper object. These top-level PolyNodes may contain their own PolyNode children representing hole polygons that may also contain children representing nested outer polygons etc.

Since the PolyTree data structure is more complex than the alternative Polygons data structure that's passed to Clipper's overloaded Execute method, and because it's more computationally expensive to process (roughly 5-10% slower), it should only be used when parent-child polygon relationships are needed and not just polygon coordinates.

+ + + + + + + + + + +
+ + + + +
+
+    polytree: 
+    Contour = ()
+    ChildCount = 1
+    Childs[0]: 
+        Contour = ((10,10),(100,10),(100,100),(10,100))
+        IsHole = False
+        ChildCount = 1
+        Childs[0]: 
+            Contour = ((20,20),(20,90),(90,90),(90,20))
+            IsHole = True
+            ChildCount = 2
+            Childs[0]: 
+                Contour = ((30,30),(50,30),(50,50),(30,50))
+                IsHole = False
+                ChildCount = 0
+            Childs[1]: 
+                Contour = ((60,60),(80,60),(80,80),(60,80))
+                IsHole = False
+                ChildCount = 0
+
+            
+ +

+ +

Reference

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fields + Methods + Properties +
In PolyTree: +
+ Clear + Total +
+ GetFirst + +
In PolyNode: +
+ GetNext + ChildCount +
+ + Childs +
+ + Contour +
+ + IsHole +
+ + Parent +
+

See Also

+

Clipper.Execute, PolyNode, ExPolygons, Polygons

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Routines/Area.htm b/clipper/Documentation/Docs/Units/ClipperLib/Routines/Area.htm new file mode 100755 index 0000000..1d9de0b --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Routines/Area.htm @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + Area + + + + + + + + + + + + + + + + + + + + + +

Area

+ + +

Del.» function Area(const pts: TPolygon): double;

+ +

C++ » double Area(const Polygon &poly);

+ +

C#  » public static double Area(Polygon poly);

+ + +

This function returns the area of the supplied polygon. Depending on orientation, this value may be positive or negative. If Orientation is true, then the area will be positive and conversely, if Orientation is false, then the area will be negative.

+
+ + +

See Also

+

Orientation, Polygon

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Routines/CleanPolygon.htm b/clipper/Documentation/Docs/Units/ClipperLib/Routines/CleanPolygon.htm new file mode 100755 index 0000000..73fa70c --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Routines/CleanPolygon.htm @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + CleanPolygon + + + + + + + + + + + + + + + + + + + + + +

CleanPolygon

+ + +

Del.» function CleanPolygon(const Poly: TPolygon; Distance: double = 1.415): TPolygon;

+ +

C++ » void CleanPolygon(Polygon &in_poly, Polygon &out_poly, double distance = 1.415);

+ +

C#  » public static Polygon CleanPolygon(Polygon poly, double distance = 1.415);

+
+ + +

This function removes vertices that are within the specified 'distance' from their preceeding vertices. The default distance is approx SQRT(2) so that adjacent vertices having their corresponding X and Y coordinates within 1 unit of each other will precipitate the removal of the second vertex.

+
+ + + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Routines/CleanPolygons.htm b/clipper/Documentation/Docs/Units/ClipperLib/Routines/CleanPolygons.htm new file mode 100755 index 0000000..1b18b88 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Routines/CleanPolygons.htm @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + CleanPolygons + + + + + + + + + + + + + + + + + + + + + +

CleanPolygons

+ + +

Del.» function CleanPolygons(const Polys: TPolygons; Distance: double = 1.415): TPolygons;

+ +

C++ » void CleanPolygons(Polygons &in_polys, Polygon &out_polys, double distance = 1.415);

+ +

C#  » public static Polygons CleanPolygons(Polygons polys, double distance = 1.415);

+
+ + +

This function removes vertices that are within the specified 'distance' from their preceeding vertices. The default distance is approx SQRT(2) so that adjacent vertices having their corresponding X and Y coordinates within 1 unit of each other will precipitate the removal of the second vertex.

+
+ + + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Routines/OffsetPolygons.htm b/clipper/Documentation/Docs/Units/ClipperLib/Routines/OffsetPolygons.htm new file mode 100755 index 0000000..4a4109e --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Routines/OffsetPolygons.htm @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + OffsetPolygons + + + + + + + + + + + + + + + + + + + + + +

OffsetPolygons

+ + +

Del.» function OffsetPolygons(const polys: TPolygons; const delta: double; JoinType: TJoinType = jtSquare; MiterLimit: double = 2.0; AutoFix: boolean = true): TPolygons;

+ +

C++ » void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, double delta, JoinType jointype = jtSquare, double MiterLimit = 2.0, bool AutoFix = true);

+ +

C#  » public static Polygons OffsetPolygons(Polygons polys, double delta, JoinType jointype = JoinType.jtSquare, double MiterLimit = 2.0, bool AutoFix = true);

+
+ + +

This function offsets the 'polys' polygons parameter by the 'delta' amount. Positive delta values expand outer polygons and contract inner 'hole' polygons. Negative deltas do the reverse.

+
+ + +

Edge joins may be one of three jointypes - jtMiter, jtSquare or jtRound. If the jointype is jtMiter, then the MiterLimit parameter will determine the maximum distance from the original vertex that the new offsetted vertex is allowed (in multiples of delta) before squaring is applied.

+
+ + +

It's important that the polygons passed to this function are oriented such that outer polygons have a 'true' orientation and inner 'hole' polygons have a 'false' orientation. If the orientations of input polygons are incorrect, the function will return unexpected results.

The optional AutoFix parameter will check the orientations of the input polygons and reverse them if needed. (AutoFix is optional because it is time-consuming. If the orientations of the input polygons are known to be correct and duplicate vertices don't need stripping, then set this parameter to false.)

+
+ + +

+
+ + +

See Also

+

Orientation, JoinType

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Routines/Orientation.htm b/clipper/Documentation/Docs/Units/ClipperLib/Routines/Orientation.htm new file mode 100755 index 0000000..35b52d4 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Routines/Orientation.htm @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + Orientation + + + + + + + + + + + + + + + + + + + + + +

Orientation

+ + +

Del.» function Orientation(const poly: TPolygon): boolean;

+ +

C++ » bool Orientation(const Polygon &poly); // Function in the ClipperLib namespace.

+ +

C#  » public static bool Orientation(Polygon poly); // Static method of the Clipper class in the ClipperLib namespace.

+ + +

Orientation returns a boolean value that is based on the polygon's orientation relative to the display's orientation (ie Y-axis positive upward vs Y-axis positive downward). +

    + +
  • On Y-axis positive upward displays, Orientation will return true if the polygon's orientation is counter-clockwise.
  • + +
  • On Y-axis positive downward displays, Orientation will return true if the polygon's orientation is clockwise.
  • + +

+ + +

Notes:
+

    + +
  • Duplicate coordinates in 'poly' must be removed before calling this function (including duplicate start & end nodes).
  • + +
  • The majority of 2D graphic display libraries (eg GDI, GDI+, XLib, Cairo, AGG, Graphics32) and even the SVG file format have their coordinate origins at the top-left corner of their respective viewports with their Y axes increasing downward. However, some display libraries (eg Quartz, OpenGL) have their coordinate origins undefined or in the classic bottom-left position with their Y axes increasing upward.
  • + +
  • It doesn't matter which Orientation is used by the polygons that are passed to Clipper objects - as long as polygon 'holes' are oriented opposite to outer polygons. Also it doesn't matter if Subject and Clip polygons have opposite orientations, as long as all Subject polygons have consistent outer and inner orientations, and all Clip polygons do likewise.
  • + +
  • The Orientations of polygon 'solutions' returned by Clipper's Execute method will always be 'true' for outer polygons and 'false' for inner 'hole' polygons (unless the ReverseSolution property has been enabled).
  • + +
  • For the OffsetPolygons function to work as expected, outer polygons must have a 'true' Orientation and polygon holes must have a 'false' Orientation.
  • + +
  • Self-intersecting polygons have indeterminate orientations, so passing a self-intersecting polygon to this function will deliver unpredictable results.
  • + +

+
+ + +

See Also

+

Clipper.ReverseSolution, OffsetPolygons, Polygon

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Routines/ReversePolygon.htm b/clipper/Documentation/Docs/Units/ClipperLib/Routines/ReversePolygon.htm new file mode 100755 index 0000000..638b54a --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Routines/ReversePolygon.htm @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + ReversePolygon + + + + + + + + + + + + + + + + + + + + + +

ReversePolygon

+ + +

Del.» function ReversePolygon(const polys: TPolygon): TPolygon;

+ +

C++ » void ReversePolygon(const Polygon &p);

+ +

C#  » //Call Polygon.Reverse().

+
+ + + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Routines/ReversePolygons.htm b/clipper/Documentation/Docs/Units/ClipperLib/Routines/ReversePolygons.htm new file mode 100755 index 0000000..23c3362 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Routines/ReversePolygons.htm @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + ReversePolygons + + + + + + + + + + + + + + + + + + + + + +

ReversePolygons

+ + +

Del.» function ReversePolygons(const polys: TPolygons): TPolygons;

+ +

C++ » void ReversePolygons(const Polygons &p);

+ +

C#  » void ReversePolygons( Polygons polys );

+
+ + + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Routines/SimplifyPolygon.htm b/clipper/Documentation/Docs/Units/ClipperLib/Routines/SimplifyPolygon.htm new file mode 100755 index 0000000..15f37d7 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Routines/SimplifyPolygon.htm @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + SimplifyPolygon + + + + + + + + + + + + + + + + + + + + + +

SimplifyPolygon

+ + +

Del.» function SimplifyPolygon(const poly: TPolygon): TPolygons;

+ +

C++ » void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys);

+ +

C#  » public static Polygons SimplifyPolygon(Polygon poly);

+
+ + +

This function converts a self-intersecting polygon into simple polygons.

+
+ + + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Routines/SimplifyPolygons.htm b/clipper/Documentation/Docs/Units/ClipperLib/Routines/SimplifyPolygons.htm new file mode 100755 index 0000000..515a68e --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Routines/SimplifyPolygons.htm @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + SimplifyPolygons + + + + + + + + + + + + + + + + + + + + + +

SimplifyPolygons

+ + +

Del.» function SimplifyPolygons(const polys: TPolygons): TPolygons;

+ +

C++ » void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys);

+ +

C++ » void SimplifyPolygons(const Polygons &polys);

+ +

C#  » public static Polygons SimplifyPolygons(Polygons polys);

+
+ + +

This function converts self-intersecting polygons into simple polygons.

+
+ + + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Types/ClipType.htm b/clipper/Documentation/Docs/Units/ClipperLib/Types/ClipType.htm new file mode 100755 index 0000000..abce7a2 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Types/ClipType.htm @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + ClipType + + + + + + + + + + + + + + + + + + + + + +

ClipType

+ + +

Del.» type TClipType = (ctIntersection, ctUnion, ctDifference, ctXor);

+ +

C++ » enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };

+ +

C#  » public enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };

+
+ + +



     

+ + + + +

See Also

+

Clipper.Execute

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Types/ExPolygons.htm b/clipper/Documentation/Docs/Units/ClipperLib/Types/ExPolygons.htm new file mode 100755 index 0000000..659c738 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Types/ExPolygons.htm @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + ExPolygons + + + + + + + + + + + + + + + + + + + + + +

ExPolygons

+ + + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Types/IntPoint.htm b/clipper/Documentation/Docs/Units/ClipperLib/Types/IntPoint.htm new file mode 100755 index 0000000..a99e355 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Types/IntPoint.htm @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + IntPoint + + + + + + + + + + + + + + + + + + + + + +

IntPoint

+ +

Del.» TIntPoint = record X, Y: int64; end;

+ +

C++ » struct IntPoint { long64 X; long64 Y; ... };

+ +

C#  » public class IntPoint { public Int64 X; { get; set; } public Int64 Y; { get; set; } ... };

+
+ + + + + +

See Also

+

long64, Polygon, Polygons

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Types/IntRect.htm b/clipper/Documentation/Docs/Units/ClipperLib/Types/IntRect.htm new file mode 100755 index 0000000..e4196c4 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Types/IntRect.htm @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + IntRect + + + + + + + + + + + + + + + + + + + + + +

IntRect

+ +

Del.»
TIntRect = record left, top, right, bottom: Int64; end;

+ +

C++ »
struct IntPoint { long64 left; long64 top; long64 right; long64 bottom; ... };

+ +

C#  »
public class IntPoint {
  public Int64 left; { get; set; }
  public Int64 top; { get; set; }
  public Int64 right; { get; set; }
  public Int64 bottom; { get; set; } ... };

+ +

Structure returned by Clipper's GetBounds method.


+ + +

See Also

+

ClipperBase.GetBounds, Long64

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Types/JoinType.htm b/clipper/Documentation/Docs/Units/ClipperLib/Types/JoinType.htm new file mode 100755 index 0000000..861dfea --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Types/JoinType.htm @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + JoinType + + + + + + + + + + + + + + + + + + + + + +

JoinType

+ + +

Del.» type TJoinType = (jtSquare, jtRound, jtMiter);

+ +

C++ » enum JoinType { jtSquare, jtRound, jtMiter };

+ +

C#  » public enum JoinType { jtSquare, jtRound, jtMiter };

+
+ + + + +

See Also

+

OffsetPolygons

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Types/PolyFillType.htm b/clipper/Documentation/Docs/Units/ClipperLib/Types/PolyFillType.htm new file mode 100755 index 0000000..db2384e --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Types/PolyFillType.htm @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + PolyFillType + + + + + + + + + + + + + + + + + + + + + +

PolyFillType

+ + +

Del.» type TPolyFillType = (pftEvenOdd, pftNonZero, pftPositive, pftNegative);

+ +

C++ » enum PolyFillType {pftEvenOdd, pftNonZero, pftPositive, pftNegative};

+ +

C#  » public enum PolyFillType {pftEvenOdd, pftNonZero, pftPositive, pftNegative};

+
+ + +

        

+ + +

See Also

+

Clipper.Execute

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Types/PolyType.htm b/clipper/Documentation/Docs/Units/ClipperLib/Types/PolyType.htm new file mode 100755 index 0000000..72fb93a --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Types/PolyType.htm @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + PolyType + + + + + + + + + + + + + + + + + + + + + +

PolyType

+ + +

Del.» type TPolyType = (ptSubject, ptClip);

+ +

C++ » enum PolyType { ptSubject, ptClip };

+ +

C#  » public enum PolyType { ptSubject, ptClip };

+
+ + + + +

See Also

+

ClipperBase.AddPolygon, ClipperBase.AddPolygons

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Types/Polygon.htm b/clipper/Documentation/Docs/Units/ClipperLib/Types/Polygon.htm new file mode 100755 index 0000000..26c555d --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Types/Polygon.htm @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + Polygon + + + + + + + + + + + + + + + + + + + + + +

Polygon

+ +

Del.» TPolygon = array of TIntPoint;

+ +

C++ » typedef std::vector< IntPoint > Polygon;

+ +

C#  » using Polygon = List<IntPoint>;

+
+ +

This structure contains the coordinates of a single polygon contour.

The term 'polygon' is often used by others to refer to a single 'outer' contour together with any number of inner contours or 'holes'. However, in this documentation, polygon is used synonymously with a single polygon contour, irrespective of whether it's an outer or a hole contour.

Multiple polygons (ie outer contours and hole contours) are encapuslated within a Polygons structure.

A Polygon structure can be passed to Clipper objects via the AddPolygon method.

+ +

See Also

+

Example, ClipperBase.AddPolygon, PolyTree, Orientation, IntPoint, Polygons

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Types/Polygons.htm b/clipper/Documentation/Docs/Units/ClipperLib/Types/Polygons.htm new file mode 100755 index 0000000..fa98059 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Types/Polygons.htm @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + Polygons + + + + + + + + + + + + + + + + + + + + + +

Polygons

+ + +

Del.» TPolygons = array of TPolygon;

+ +

C++ » typedef std::vector< Polygon > Polygons;

+ +

C#  » using Polygons = List<List< IntPoint >>;

+
+ +

This structure encapsulates one or a number of outer and inner 'hole' Polygon contours (where inner polygon contours have an Orientation opposite that of outer contours).

Polygons can be passed to Clipper objects via the AddPolygons method.

The solution parameter in Clipper's overloaded Execute method can return either a Polygons structure or a PolyTree structure.

+
+ + +

See Also

+

Clipper.Execute, ClipperBase.AddPolygons, PolyTree, Orientation, IntPoint, Polygon

+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/Types/long64.htm b/clipper/Documentation/Docs/Units/ClipperLib/Types/long64.htm new file mode 100755 index 0000000..58d45a8 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/Types/long64.htm @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + long64 + + + + + + + + + + + + + + + + + + + + + +

long64

+ + +

Del.» Equivalent to Int64

+ +

C++ » typedef signed long long long64;

+ +

C#  » Equivalent to Int64

+
+ + + + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/Units/ClipperLib/_Body.htm b/clipper/Documentation/Docs/Units/ClipperLib/_Body.htm new file mode 100755 index 0000000..5049841 --- /dev/null +++ b/clipper/Documentation/Docs/Units/ClipperLib/_Body.htm @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + ClipperLib + + + + + + + + + + + + + + + + + + + + +

ClipperLib

+Unit clipper + +

Contents

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Types + Classes + Routines +
ClipType + Clipper + Area +
ExPolygons + ClipperBase + CleanPolygon +
IntPoint + PolyNode + CleanPolygons +
IntRect + PolyTree + OffsetPolygons +
JoinType + + Orientation +
long64 + + ReversePolygon +
PolyFillType + + ReversePolygons +
Polygon + + SimplifyPolygon +
Polygons + + SimplifyPolygons +
PolyType + + +
+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Docs/_Body.htm b/clipper/Documentation/Docs/_Body.htm new file mode 100755 index 0000000..9e907c1 --- /dev/null +++ b/clipper/Documentation/Docs/_Body.htm @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + The Clipper Library + + + + + + + + + + + + + + + + + + + + + +

The Clipper Library - Version 5

+ + + + + + + + + + + +
Library Overview + Changes + Examples + FAQ + Rounding + Compatibility with Prior Versions + License +
+ + + + + +
+ +

Classes (Hierarchy)

+

 ClipperBase

+

 Clipper

+

 PolyNode

+

 PolyTree

+
+

Types

+ + + + + + + + + + + + + + + +
ClipType + IntPoint + JoinType + PolyFillType + Polygons +
ExPolygons + IntRect + long64 + Polygon + PolyType +
+

Routines

+ + + + + + + + + + + + + + +
Area + CleanPolygons + Orientation + ReversePolygons + SimplifyPolygons +
CleanPolygon + OffsetPolygons + ReversePolygon + SimplifyPolygon +
+

Units

+ + + + +
ClipperLib +
+ + + + + + \ No newline at end of file diff --git a/clipper/Documentation/Images/_BranchEmpty.gif b/clipper/Documentation/Images/_BranchEmpty.gif new file mode 100755 index 0000000..1069e82 Binary files /dev/null and b/clipper/Documentation/Images/_BranchEmpty.gif differ diff --git a/clipper/Documentation/Images/_BranchRight.gif b/clipper/Documentation/Images/_BranchRight.gif new file mode 100755 index 0000000..e5622df Binary files /dev/null and b/clipper/Documentation/Images/_BranchRight.gif differ diff --git a/clipper/Documentation/Images/_BranchVert.gif b/clipper/Documentation/Images/_BranchVert.gif new file mode 100755 index 0000000..b922f33 Binary files /dev/null and b/clipper/Documentation/Images/_BranchVert.gif differ diff --git a/clipper/Documentation/Images/_BranchVertRight.gif b/clipper/Documentation/Images/_BranchVertRight.gif new file mode 100755 index 0000000..ae0f3d1 Binary files /dev/null and b/clipper/Documentation/Images/_BranchVertRight.gif differ diff --git a/clipper/Documentation/Images/_Class.gif b/clipper/Documentation/Images/_Class.gif new file mode 100755 index 0000000..5e90e34 Binary files /dev/null and b/clipper/Documentation/Images/_Class.gif differ diff --git a/clipper/Documentation/Images/_Home.gif b/clipper/Documentation/Images/_Home.gif new file mode 100755 index 0000000..7ca8ba2 Binary files /dev/null and b/clipper/Documentation/Images/_Home.gif differ diff --git a/clipper/Documentation/Images/_Project_Logo.gif b/clipper/Documentation/Images/_Project_Logo.gif new file mode 100755 index 0000000..7e3baac Binary files /dev/null and b/clipper/Documentation/Images/_Project_Logo.gif differ diff --git a/clipper/Documentation/Images/_Unit.gif b/clipper/Documentation/Images/_Unit.gif new file mode 100755 index 0000000..ef56412 Binary files /dev/null and b/clipper/Documentation/Images/_Unit.gif differ diff --git a/clipper/Documentation/Images/_buttons.gif b/clipper/Documentation/Images/_buttons.gif new file mode 100755 index 0000000..7daf3cd Binary files /dev/null and b/clipper/Documentation/Images/_buttons.gif differ diff --git a/clipper/Documentation/Images/clipper_rounding.png b/clipper/Documentation/Images/clipper_rounding.png new file mode 100755 index 0000000..1fa5ddc Binary files /dev/null and b/clipper/Documentation/Images/clipper_rounding.png differ diff --git a/clipper/Documentation/Images/clipper_rounding2.png b/clipper/Documentation/Images/clipper_rounding2.png new file mode 100755 index 0000000..8b63736 Binary files /dev/null and b/clipper/Documentation/Images/clipper_rounding2.png differ diff --git a/clipper/Documentation/Images/clipper_rounding3.png b/clipper/Documentation/Images/clipper_rounding3.png new file mode 100755 index 0000000..40d8314 Binary files /dev/null and b/clipper/Documentation/Images/clipper_rounding3.png differ diff --git a/clipper/Documentation/Images/clipper_rounding4.png b/clipper/Documentation/Images/clipper_rounding4.png new file mode 100755 index 0000000..8d5c60d Binary files /dev/null and b/clipper/Documentation/Images/clipper_rounding4.png differ diff --git a/clipper/Documentation/Images/cliptype.png b/clipper/Documentation/Images/cliptype.png new file mode 100755 index 0000000..ce9ec4d Binary files /dev/null and b/clipper/Documentation/Images/cliptype.png differ diff --git a/clipper/Documentation/Images/difference.png b/clipper/Documentation/Images/difference.png new file mode 100755 index 0000000..c2bce6c Binary files /dev/null and b/clipper/Documentation/Images/difference.png differ diff --git a/clipper/Documentation/Images/evenodd.png b/clipper/Documentation/Images/evenodd.png new file mode 100755 index 0000000..740974d Binary files /dev/null and b/clipper/Documentation/Images/evenodd.png differ diff --git a/clipper/Documentation/Images/int.png b/clipper/Documentation/Images/int.png new file mode 100755 index 0000000..7df1cea Binary files /dev/null and b/clipper/Documentation/Images/int.png differ diff --git a/clipper/Documentation/Images/intersection.png b/clipper/Documentation/Images/intersection.png new file mode 100755 index 0000000..fd08dd0 Binary files /dev/null and b/clipper/Documentation/Images/intersection.png differ diff --git a/clipper/Documentation/Images/jointypes.png b/clipper/Documentation/Images/jointypes.png new file mode 100755 index 0000000..2cd93e9 Binary files /dev/null and b/clipper/Documentation/Images/jointypes.png differ diff --git a/clipper/Documentation/Images/kangaroo_small.png b/clipper/Documentation/Images/kangaroo_small.png new file mode 100755 index 0000000..25b2df8 Binary files /dev/null and b/clipper/Documentation/Images/kangaroo_small.png differ diff --git a/clipper/Documentation/Images/negative.png b/clipper/Documentation/Images/negative.png new file mode 100755 index 0000000..3330459 Binary files /dev/null and b/clipper/Documentation/Images/negative.png differ diff --git a/clipper/Documentation/Images/nonzero.png b/clipper/Documentation/Images/nonzero.png new file mode 100755 index 0000000..5cd1214 Binary files /dev/null and b/clipper/Documentation/Images/nonzero.png differ diff --git a/clipper/Documentation/Images/polyline_offset.png b/clipper/Documentation/Images/polyline_offset.png new file mode 100755 index 0000000..a2efb18 Binary files /dev/null and b/clipper/Documentation/Images/polyline_offset.png differ diff --git a/clipper/Documentation/Images/polytree.png b/clipper/Documentation/Images/polytree.png new file mode 100755 index 0000000..3e0d460 Binary files /dev/null and b/clipper/Documentation/Images/polytree.png differ diff --git a/clipper/Documentation/Images/positive.png b/clipper/Documentation/Images/positive.png new file mode 100755 index 0000000..eface7f Binary files /dev/null and b/clipper/Documentation/Images/positive.png differ diff --git a/clipper/Documentation/Images/sample1.png b/clipper/Documentation/Images/sample1.png new file mode 100755 index 0000000..7c77a04 Binary files /dev/null and b/clipper/Documentation/Images/sample1.png differ diff --git a/clipper/Documentation/Images/union.png b/clipper/Documentation/Images/union.png new file mode 100755 index 0000000..6f7a3a6 Binary files /dev/null and b/clipper/Documentation/Images/union.png differ diff --git a/clipper/Documentation/Images/xor.png b/clipper/Documentation/Images/xor.png new file mode 100755 index 0000000..9468349 Binary files /dev/null and b/clipper/Documentation/Images/xor.png differ diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/LGPL-LICENSE b/clipper/Documentation/Scripts/SyntaxHighlighter/LGPL-LICENSE new file mode 100755 index 0000000..3f9959f --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/LGPL-LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/MIT-LICENSE b/clipper/Documentation/Scripts/SyntaxHighlighter/MIT-LICENSE new file mode 100755 index 0000000..e7c70ba --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2003, 2004 Jim Weirich + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/_theme_template.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/_theme_template.scss new file mode 100755 index 0000000..53f4df5 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/_theme_template.scss @@ -0,0 +1,120 @@ +$background: white !default; + +$line_alt1_background: $background !default; +$line_alt2_background: $background !default; + +$line_highlighted_background: #e0e0e0 !default; +$line_highlighted_number: black !default; + +$gutter_text: #afafaf !default; +$gutter_border_color: #6ce26c !default; +$gutter_border: 3px solid $gutter_border_color !default; + +$toolbar_collapsed_a: #00f !default; +$toolbar_collapsed_a_hover: #f00 !default; +$toolbar_collapsed_background: #fff !default; +$toolbar_collapsed_border: 1px solid $gutter_border_color !default; + +$toolbar_a: #fff !default; +$toolbar_a_hover: #000 !default; +$toolbar_background: $gutter_border_color !default; +$toolbar_border: none !default; + +$code_plain: black !default; +$code_comments: #008200 !default; +$code_string: blue !default; +$code_keyword: #006699 !default; +$code_preprocessor: gray !default; +$code_variable: #aa7700 !default; +$code_value: #009900 !default; +$code_functions: #ff1493 !default; +$code_constants: #0066cc !default; +$code_script: $code_keyword !default; +$code_script_background: none !default; +$code_color1: gray !default; +$code_color2: #ff1493 !default; +$code_color3: red !default; + +$caption_color: $code_plain !default; + +// Interface elements. +.syntaxhighlighter { + background-color: $background !important; + + // Highlighed line number + .line { + &.alt1 { background-color: $line_alt1_background !important; } + &.alt2 { background-color: $line_alt2_background !important; } + + // Highlighed line + &.highlighted { + &.alt1, &.alt2 { background-color: $line_highlighted_background !important; } + &.number { color: $line_highlighted_number !important; } + } + } + + table { + caption { + color: $caption_color !important; + } + } + + // Add border to the lines + .gutter { + color: $gutter_text !important; + .line { + border-right: $gutter_border !important; + + &.highlighted { + background-color: $gutter_border_color !important; + color: $background !important; + } + } + } + + &.printing .line .content { border: none !important; } + + &.collapsed { + overflow: visible !important; + + .toolbar { + color: $toolbar_collapsed_a !important; + background: $toolbar_collapsed_background !important; + border: $toolbar_collapsed_border !important; + + a { + color: $toolbar_collapsed_a !important; + &:hover { color: $toolbar_collapsed_a_hover !important; } + } + } + } + + .toolbar { + color: $toolbar_a !important; + background: $toolbar_background !important; + border: $toolbar_border !important; + a { + color: $toolbar_a !important; + &:hover { color: $toolbar_a_hover !important; } + } + } + + // Actual syntax highlighter colors. + .plain, .plain a { color: $code_plain !important; } + .comments, .comments a { color: $code_comments !important; } + .string, .string a { color: $code_string !important; } + .keyword { color: $code_keyword !important; } + .preprocessor { color: $code_preprocessor !important; } + .variable { color: $code_variable !important; } + .value { color: $code_value !important; } + .functions { color: $code_functions !important; } + .constants { color: $code_constants !important; } + .script { + font-weight: bold !important; + color: $code_script !important; + background-color: $code_script_background !important; + } + .color1, .color1 a { color: $code_color1 !important; } + .color2, .color2 a { color: $code_color2 !important; } + .color3, .color3 a { color: $code_color3 !important; } +} diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/config.rb b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/config.rb new file mode 100755 index 0000000..6f82de1 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/config.rb @@ -0,0 +1,14 @@ +environment = :production +project_type = :stand_alone +http_path = "/" +css_dir = "../styles" +sass_dir = "." +images_dir = "images" +sass_options = { + :line_numbers => false, + :debug_info => false +} + +# output_style = :compressed +# output_style = :compact +output_style = :expanded diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCore.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCore.scss new file mode 100755 index 0000000..a67e4f9 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCore.scss @@ -0,0 +1,216 @@ +@mixin round_corners_custom($top, $right, $bottom, $left) { + -moz-border-radius: $top $right $bottom $left !important; + -webkit-border-radius: $top $right $bottom $left !important; +} + +@mixin round_corners($radius) { + @include round_corners_custom($radius, $radius, $radius, $radius); +} + +.syntaxhighlighter { + a, + div, + code, + table, + table td, + table tr, + table tbody, + table thead, + table caption, + textarea { + @include round_corners(0); + + background: none !important; + border: 0 !important; + bottom: auto !important; + float: none !important; + height: auto !important; + left: auto !important; + line-height: 1.1em !important; + margin: 0 !important; + outline: 0 !important; + overflow: visible !important; + padding: 0 !important; + position: static !important; + right: auto !important; + text-align: left !important; + top: auto !important; + vertical-align: baseline !important; + width: auto !important; + box-sizing: content-box !important; + font: { + family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; + weight: normal !important; + style: normal !important; + size: 1em !important; + } + min: { + // For IE8, FF & WebKit + height: inherit !important; + // For IE7 + height: auto !important; + } + } +} + +.syntaxhighlighter { + width: 100% !important; + margin: 1em 0 1em 0 !important; + + position: relative !important; + overflow: auto !important; + font-size: 1em !important; + + &.source { overflow: hidden !important; } + + // set up bold and italic + .bold { font-weight: bold !important; } + .italic { font-style: italic !important; } + + .line { white-space: pre !important; } + + // main table and columns + table { + width: 100% !important; + caption { + text-align: left !important; + padding: .5em 0 0.5em 1em !important; + } + + td.code { + width: 100% !important; + + .container { + position: relative !important; + + textarea { + box-sizing: border-box !important; + position: absolute !important; + left: 0 !important; + top: 0 !important; + width: 100% !important; + height: 100% !important; + border: none !important; + background: white !important; + padding-left: 1em !important; + overflow: hidden !important; + white-space: pre !important; + } + } + } + + // middle spacing between line numbers and lines + td.gutter .line { + text-align: right !important; + padding: 0 0.5em 0 1em !important; + } + + td.code .line { + padding: 0 1em !important; + } + } + + &.nogutter { + td.code { + .container textarea, .line { padding-left: 0em !important; } + } + } + + &.show { display: block !important; } + + // Adjust some properties when collapsed + &.collapsed { + table { display: none !important; } + + .toolbar { + padding: 0.1em 0.8em 0em 0.8em !important; + font-size: 1em !important; + position: static !important; + width: auto !important; + height: auto !important; + + span { + display: inline !important; + margin-right: 1em !important; + + a { + padding: 0 !important; + display: none !important; + &.expandSource { display: inline !important; } + } + } + } + } + + // Styles for the toolbar + .toolbar { + position: absolute !important; + right: 1px !important; + top: 1px !important; + width: 11px !important; + height: 11px !important; + font-size: 10px !important; + z-index: 10 !important; + + span.title { display: inline !important; } + + a { + display: block !important; + text-align: center !important; + text-decoration: none !important; + padding-top: 1px !important; + + &.expandSource { display: none !important; } + } + } + + &.ie { + font-size: .9em !important; + padding: 1px 0 1px 0 !important; + + .toolbar { + line-height: 8px !important; + a { + padding-top: 0px !important; + } + } + } + + // Print view. + // Colors are based on the default theme without background. + &.printing { + .line.alt1 .content, + .line.alt2 .content, + .line.highlighted .number, + .line.highlighted.alt1 .content, + .line.highlighted.alt2 .content { background: none !important; } + + // Gutter line numbers + .line { + .number { color: #bbbbbb !important; } + // Add border to the lines + .content { color: black !important; } + } + + // Toolbar when visible + .toolbar { display: none !important; } + a { text-decoration: none !important; } + .plain, .plain a { color: black !important; } + .comments, .comments a { color: #008200 !important; } + .string, .string a { color: blue !important; } + .keyword { + color: #006699 !important; + font-weight: bold !important; + } + .preprocessor { color: gray !important; } + .variable { color: #aa7700 !important; } + .value { color: #009900 !important; } + .functions { color: #ff1493 !important; } + .constants { color: #0066cc !important; } + .script { font-weight: bold !important; } + .color1, .color1 a { color: gray !important; } + .color2, .color2 a { color: #ff1493 !important; } + .color3, .color3 a { color: red !important; } + .break, .break a { color: black !important; } + } +} \ No newline at end of file diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreDefault.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreDefault.scss new file mode 100755 index 0000000..ff80c7f --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreDefault.scss @@ -0,0 +1,2 @@ +@import "shCore.scss"; +@import "shThemeDefault.scss"; diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreDjango.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreDjango.scss new file mode 100755 index 0000000..ef572e9 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreDjango.scss @@ -0,0 +1,2 @@ +@import "shCore.scss"; +@import "shThemeDjango.scss"; diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreEclipse.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreEclipse.scss new file mode 100755 index 0000000..9767f53 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreEclipse.scss @@ -0,0 +1,2 @@ +@import "shCore.scss"; +@import "shThemeEclipse.scss"; diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreEmacs.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreEmacs.scss new file mode 100755 index 0000000..5e466f3 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreEmacs.scss @@ -0,0 +1,2 @@ +@import "shCore.scss"; +@import "shThemeEmacs.scss"; diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreFadeToGrey.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreFadeToGrey.scss new file mode 100755 index 0000000..4628595 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreFadeToGrey.scss @@ -0,0 +1,2 @@ +@import "shCore.scss"; +@import "shThemeFadeToGrey.scss"; diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreMDUltra.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreMDUltra.scss new file mode 100755 index 0000000..10ad4c5 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreMDUltra.scss @@ -0,0 +1,2 @@ +@import "shCore.scss"; +@import "shThemeMDUltra.scss"; diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreMidnight.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreMidnight.scss new file mode 100755 index 0000000..e357eb2 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreMidnight.scss @@ -0,0 +1,2 @@ +@import "shCore.scss"; +@import "shThemeMidnight.scss"; diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreRDark.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreRDark.scss new file mode 100755 index 0000000..5c26da3 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shCoreRDark.scss @@ -0,0 +1,2 @@ +@import "shCore.scss"; +@import "shThemeRDark.scss"; diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeDefault.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeDefault.scss new file mode 100755 index 0000000..1574dae --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeDefault.scss @@ -0,0 +1,7 @@ +// Default Syntax Highlighter theme. + +@import "_theme_template.scss"; + +.syntaxhighlighter { + .keyword { font-weight: bold !important; } +} diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeDjango.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeDjango.scss new file mode 100755 index 0000000..8e95c56 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeDjango.scss @@ -0,0 +1,36 @@ +// Django SyntaxHighlighter theme + +$background: #0a2b1d !default; + +$line_highlighted_background: #233729 !default; +$line_highlighted_number: white !default; + +$gutter_text: #497958 !default; +$gutter_border_color: #41a83e !default; + +$toolbar_collapsed_a: #96dd3b !default; +$toolbar_collapsed_a_hover: #fff !default; +$toolbar_collapsed_background: #000 !default; + +$toolbar_a: #fff !default; +$toolbar_a_hover: #ffe862 !default; + +$code_plain: #f8f8f8 !default; +$code_comments: #336442 !default; +$code_string: #9df39f !default; +$code_keyword: #96dd3b !default; +$code_preprocessor: #91bb9e !default; +$code_variable: #ffaa3e !default; +$code_value: #f7e741 !default; +$code_functions: #ffaa3e !default; +$code_constants: #e0e8ff !default; +$code_color1: #eb939a !default; +$code_color2: #91bb9e !default; +$code_color3: #edef7d !default; + +@import "_theme_template.scss"; + +.syntaxhighlighter { + .comments { font-style: italic !important; } + .keyword { font-weight: bold !important; } +} diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeEclipse.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeEclipse.scss new file mode 100755 index 0000000..193fb1d --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeEclipse.scss @@ -0,0 +1,48 @@ +// Eclipse IDE SyntaxHighlighter color theme +// (C) Code-House +// :http//blog.code-house.org/2009/10/xml-i-adnotacje-kod-ogolnego-przeznaczenia-i-jpa/ + +$background: #fff !default; + +$line_highlighted_background: #c3defe !default; +$line_highlighted_number: #fff !default; + +$gutter_text: #787878 !default; +$gutter_border_color: #d4d0c8 !default; + +$toolbar_collapsed_a: #3f5fbf !default; +$toolbar_collapsed_a_hover: #aa7700 !default; +$toolbar_collapsed_background: #fff !default; + +$toolbar_a: #a0a0a0 !default; +$toolbar_a_hover: red !default; + +$code_plain: black !default; +$code_comments: #3f5fbf !default; +$code_string: #2a00ff !default; +$code_keyword: #7f0055 !default; +$code_preprocessor: #646464 !default; +$code_variable: #aa7700 !default; +$code_value: #009900 !default; +$code_functions: #ff1493 !default; +$code_constants: #0066cc !default; +$code_color1: gray !default; +$code_color2: #ff1493 !default; +$code_color3: red !default; + +@import "_theme_template.scss"; + +.syntaxhighlighter { + .keyword { font-weight: bold !important; } + + .xml { + .keyword { + color: #3f7f7f !important; + font-weight: normal !important; } + .color1, .color1 a { color: #7f007f !important; } + .string { + font-style: italic !important; + color: #2a00ff !important; + } + } +} diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeEmacs.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeEmacs.scss new file mode 100755 index 0000000..11c9deb --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeEmacs.scss @@ -0,0 +1,32 @@ +// Emacs SyntaxHighlighter theme based on theme by Joshua Emmons +// http://www.skia.net/ + +$background: black !default; + +$line_highlighted_background: #2A3133 !default; +$line_highlighted_number: white !default; + +$gutter_text: #d3d3d3 !default; +$gutter_border_color: #990000 !default; + +$toolbar_collapsed_a: #ebdb8d !default; +$toolbar_collapsed_a_hover: #ff7d27 !default; +$toolbar_collapsed_background: black !default; + +$toolbar_a: #fff !default; +$toolbar_a_hover: #9ccff4 !default; + +$code_plain: #d3d3d3 !default; +$code_comments: #ff7d27 !default; +$code_string: #ff9e7b !default; +$code_keyword: aqua !default; +$code_preprocessor: #aec4de !default; +$code_variable: #ffaa3e !default; +$code_value: #009900 !default; +$code_functions: #81cef9 !default; +$code_constants: #ff9e7b !default; +$code_color1: #ebdb8d !default; +$code_color2: #ff7d27 !default; +$code_color3: #aec4de !default; + +@import "_theme_template.scss"; diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeFadeToGrey.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeFadeToGrey.scss new file mode 100755 index 0000000..7963814 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeFadeToGrey.scss @@ -0,0 +1,36 @@ +// Fade to Grey SyntaxHighlighter theme based on theme by Brasten Sager +// :http//www.ibrasten.com/ + +$background: #121212 !default; + +$line_highlighted_background: #2C2C29 !default; +$line_highlighted_number: white !default; + +$gutter_text: #afafaf !default; +$gutter_border_color: #3185b9 !default; + +$toolbar_collapsed_a: #3185b9 !default; +$toolbar_collapsed_a_hover: #d01d33 !default; +$toolbar_collapsed_background: black !default; + +$toolbar_a: #fff !default; +$toolbar_a_hover: #96daff !default; + +$code_plain: white !default; +$code_comments: #696854 !default; +$code_string: #e3e658 !default; +$code_keyword: #d01d33 !default; +$code_preprocessor: #435a5f !default; +$code_variable: #898989 !default; +$code_value: #009900 !default; +$code_functions: #aaaaaa !default; +$code_constants: #96daff !default; +$code_color1: #ffc074 !default; +$code_color2: #4a8cdb !default; +$code_color3: #96daff !default; + +@import "_theme_template.scss"; + +.syntaxhighlighter { + .functions { font-weight: bold !important; } +} diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeMDUltra.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeMDUltra.scss new file mode 100755 index 0000000..0356fa6 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeMDUltra.scss @@ -0,0 +1,32 @@ +// MDUltra SyntaxHighlighter theme based on Midnight Theme +// http://www.mddev.co.uk/ + +$background: #222222 !default; + +$line_highlighted_background: #253e5a !default; +$line_highlighted_number: white !default; + +$gutter_text: #38566f !default; +$gutter_border_color: #435a5f !default; + +$toolbar_collapsed_a: #428bdd !default; +$toolbar_collapsed_a_hover: lime !default; +$toolbar_collapsed_background: black !default; + +$toolbar_a: #aaaaff !default; +$toolbar_a_hover: #9ccff4 !default; + +$code_plain: lime !default; +$code_comments: #428bdd !default; +$code_string: lime !default; +$code_keyword: #aaaaff !default; +$code_preprocessor: #8aa6c1 !default; +$code_variable: aqua !default; +$code_value: #f7e741 !default; +$code_functions: #ff8000 !default; +$code_constants: yellow !default; +$code_color1: red !default; +$code_color2: yellow !default; +$code_color3: #ffaa3e !default; + +@import "_theme_template.scss"; diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeMidnight.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeMidnight.scss new file mode 100755 index 0000000..a4dae02 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeMidnight.scss @@ -0,0 +1,32 @@ +// Midnight SyntaxHighlighter theme based on theme by J.D. Myers +// http://webdesign.lsnjd.com/ + +$background: #0f192a !default; + +$line_highlighted_background: #253e5a !default; +$line_highlighted_number: #38566f !default; + +$gutter_text: #afafaf !default; +$gutter_border_color: #435a5f !default; + +$toolbar_collapsed_a: #428bdd !default; +$toolbar_collapsed_a_hover: #1dc116 !default; +$toolbar_collapsed_background: #000 !default; + +$toolbar_a: #D1EDFF !default; +$toolbar_a_hover: #8aa6c1 !default; + +$code_plain: #d1edff !default; +$code_comments: #428bdd !default; +$code_string: #1dc116 !default; +$code_keyword: #b43d3d !default; +$code_preprocessor: #8aa6c1 !default; +$code_variable: #ffaa3e !default; +$code_value: #f7e741 !default; +$code_functions: #ffaa3e !default; +$code_constants: #e0e8ff !default; +$code_color1: #f8bb00 !default; +$code_color2: white !default; +$code_color3: #ffaa3e !default; + +@import "_theme_template.scss"; diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeRDark.scss b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeRDark.scss new file mode 100755 index 0000000..3b67b15 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/compass/shThemeRDark.scss @@ -0,0 +1,32 @@ +// RDark SyntaxHighlighter theme based on theme by Radu Dineiu +// http://www.vim.org/scripts/script.php?script_id=1732 + +$background: #1b2426 !default; + +$line_highlighted_background: #323E41 !default; +$line_highlighted_number: #b9bdb6 !default; + +$gutter_text: #afafaf !default; +$gutter_border_color: #435a5f !default; + +$toolbar_collapsed_a: #5ba1cf !default; +$toolbar_collapsed_a_hover: #5ce638 !default; +$toolbar_collapsed_background: #000 !default; + +$toolbar_a: #fff !default; +$toolbar_a_hover: #e0e8ff !default; + +$code_plain: #b9bdb6 !default; +$code_comments: #878a85 !default; +$code_string: #5ce638 !default; +$code_keyword: #5ba1cf !default; +$code_preprocessor: #435a5f !default; +$code_variable: #ffaa3e !default; +$code_value: #009900 !default; +$code_functions: #ffaa3e !default; +$code_constants: #e0e8ff !default; +$code_color1: #e0e8ff !default; +$code_color2: white !default; +$code_color3: #ffaa3e !default; + +@import "_theme_template.scss"; diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/index.html b/clipper/Documentation/Scripts/SyntaxHighlighter/index.html new file mode 100755 index 0000000..60908f4 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/index.html @@ -0,0 +1,22 @@ + + + + + Hello SyntaxHighlighter + + + + + + + + +

Hello SyntaxHighlighter

+
+function helloSyntaxHighlighter()
+{
+	return "hi!";
+}
+
+ + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shAutoloader.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shAutoloader.js new file mode 100755 index 0000000..4e29bdd --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shAutoloader.js @@ -0,0 +1,17 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('(2(){1 h=5;h.I=2(){2 n(c,a){4(1 d=0;d|<|≥|>=|≤|<=|\*|\+|-|\/|÷|\^)/g, + css: 'color2' }, + + { regex: /\b(?:and|as|div|mod|not|or|return(?!\s&)(ing)?|equals|(is(n't| not)? )?equal( to)?|does(n't| not) equal|(is(n't| not)? )?(greater|less) than( or equal( to)?)?|(comes|does(n't| not) come) (after|before)|is(n't| not)?( in)? (back|front) of|is(n't| not)? behind|is(n't| not)?( (in|contained by))?|does(n't| not) contain|contain(s)?|(start|begin|end)(s)? with|((but|end) )?(consider|ignor)ing|prop(erty)?|(a )?ref(erence)?( to)?|repeat (until|while|with)|((end|exit) )?repeat|((else|end) )?if|else|(end )?(script|tell|try)|(on )?error|(put )?into|(of )?(it|me)|its|my|with (timeout( of)?|transaction)|end (timeout|transaction))\b/g, + css: 'keyword' }, + + { regex: /\b\d+(st|nd|rd|th)\b/g, // ordinals + css: 'keyword' }, + + { regex: /\b(?:about|above|against|around|at|below|beneath|beside|between|by|(apart|aside) from|(instead|out) of|into|on(to)?|over|since|thr(ough|u)|under)\b/g, + css: 'color3' }, + + { regex: /\b(?:adding folder items to|after receiving|choose( ((remote )?application|color|folder|from list|URL))?|clipboard info|set the clipboard to|(the )?clipboard|entire contents|display(ing| (alert|dialog|mode))?|document( (edited|file|nib name))?|file( (name|type))?|(info )?for|giving up after|(name )?extension|quoted form|return(ed)?|second(?! item)(s)?|list (disks|folder)|text item(s| delimiters)?|(Unicode )?text|(disk )?item(s)?|((current|list) )?view|((container|key) )?window|with (data|icon( (caution|note|stop))?|parameter(s)?|prompt|properties|seed|title)|case|diacriticals|hyphens|numeric strings|punctuation|white space|folder creation|application(s( folder)?| (processes|scripts position|support))?|((desktop )?(pictures )?|(documents|downloads|favorites|home|keychain|library|movies|music|public|scripts|sites|system|users|utilities|workflows) )folder|desktop|Folder Action scripts|font(s| panel)?|help|internet plugins|modem scripts|(system )?preferences|printer descriptions|scripting (additions|components)|shared (documents|libraries)|startup (disk|items)|temporary items|trash|on server|in AppleTalk zone|((as|long|short) )?user name|user (ID|locale)|(with )?password|in (bundle( with identifier)?|directory)|(close|open for) access|read|write( permission)?|(g|s)et eof|using( delimiters)?|starting at|default (answer|button|color|country code|entr(y|ies)|identifiers|items|name|location|script editor)|hidden( answer)?|open(ed| (location|untitled))?|error (handling|reporting)|(do( shell)?|load|run|store) script|administrator privileges|altering line endings|get volume settings|(alert|boot|input|mount|output|set) volume|output muted|(fax|random )?number|round(ing)?|up|down|toward zero|to nearest|as taught in school|system (attribute|info)|((AppleScript( Studio)?|system) )?version|(home )?directory|(IPv4|primary Ethernet) address|CPU (type|speed)|physical memory|time (stamp|to GMT)|replacing|ASCII (character|number)|localized string|from table|offset|summarize|beep|delay|say|(empty|multiple) selections allowed|(of|preferred) type|invisibles|showing( package contents)?|editable URL|(File|FTP|News|Media|Web) [Ss]ervers|Telnet hosts|Directory services|Remote applications|waiting until completion|saving( (in|to))?|path (for|to( (((current|frontmost) )?application|resource))?)|POSIX (file|path)|(background|RGB) color|(OK|cancel) button name|cancel button|button(s)?|cubic ((centi)?met(re|er)s|yards|feet|inches)|square ((kilo)?met(re|er)s|miles|yards|feet)|(centi|kilo)?met(re|er)s|miles|yards|feet|inches|lit(re|er)s|gallons|quarts|(kilo)?grams|ounces|pounds|degrees (Celsius|Fahrenheit|Kelvin)|print( (dialog|settings))?|clos(e(able)?|ing)|(de)?miniaturized|miniaturizable|zoom(ed|able)|attribute run|action (method|property|title)|phone|email|((start|end)ing|home) page|((birth|creation|current|custom|modification) )?date|((((phonetic )?(first|last|middle))|computer|host|maiden|related) |nick)?name|aim|icq|jabber|msn|yahoo|address(es)?|save addressbook|should enable action|city|country( code)?|formatte(r|d address)|(palette )?label|state|street|zip|AIM [Hh]andle(s)?|my card|select(ion| all)?|unsaved|(alpha )?value|entr(y|ies)|group|(ICQ|Jabber|MSN) handle|person|people|company|department|icon image|job title|note|organization|suffix|vcard|url|copies|collating|pages (across|down)|request print time|target( printer)?|((GUI Scripting|Script menu) )?enabled|show Computer scripts|(de)?activated|awake from nib|became (key|main)|call method|of (class|object)|center|clicked toolbar item|closed|for document|exposed|(can )?hide|idle|keyboard (down|up)|event( (number|type))?|launch(ed)?|load (image|movie|nib|sound)|owner|log|mouse (down|dragged|entered|exited|moved|up)|move|column|localization|resource|script|register|drag (info|types)|resigned (active|key|main)|resiz(e(d)?|able)|right mouse (down|dragged|up)|scroll wheel|(at )?index|should (close|open( untitled)?|quit( after last window closed)?|zoom)|((proposed|screen) )?bounds|show(n)?|behind|in front of|size (mode|to fit)|update(d| toolbar item)?|was (hidden|miniaturized)|will (become active|close|finish launching|hide|miniaturize|move|open|quit|(resign )?active|((maximum|minimum|proposed) )?size|show|zoom)|bundle|data source|movie|pasteboard|sound|tool(bar| tip)|(color|open|save) panel|coordinate system|frontmost|main( (bundle|menu|window))?|((services|(excluded from )?windows) )?menu|((executable|frameworks|resource|scripts|shared (frameworks|support)) )?path|(selected item )?identifier|data|content(s| view)?|character(s)?|click count|(command|control|option|shift) key down|context|delta (x|y|z)|key( code)?|location|pressure|unmodified characters|types|(first )?responder|playing|(allowed|selectable) identifiers|allows customization|(auto saves )?configuration|visible|image( name)?|menu form representation|tag|user(-| )defaults|associated file name|(auto|needs) display|current field editor|floating|has (resize indicator|shadow)|hides when deactivated|level|minimized (image|title)|opaque|position|release when closed|sheet|title(d)?)\b/g, + css: 'color3' }, + + { regex: new RegExp(this.getKeywords(specials), 'gm'), css: 'color3' }, + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, + { regex: new RegExp(this.getKeywords(ordinals), 'gm'), css: 'keyword' } + ]; + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['applescript']; + + SyntaxHighlighter.brushes.AppleScript = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushBash.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushBash.js new file mode 100755 index 0000000..8c29696 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushBash.js @@ -0,0 +1,59 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + var keywords = 'if fi then elif else for do done until while break continue case function return in eq ne ge le'; + var commands = 'alias apropos awk basename bash bc bg builtin bzip2 cal cat cd cfdisk chgrp chmod chown chroot' + + 'cksum clear cmp comm command cp cron crontab csplit cut date dc dd ddrescue declare df ' + + 'diff diff3 dig dir dircolors dirname dirs du echo egrep eject enable env ethtool eval ' + + 'exec exit expand export expr false fdformat fdisk fg fgrep file find fmt fold format ' + + 'free fsck ftp gawk getopts grep groups gzip hash head history hostname id ifconfig ' + + 'import install join kill less let ln local locate logname logout look lpc lpr lprint ' + + 'lprintd lprintq lprm ls lsof make man mkdir mkfifo mkisofs mknod more mount mtools ' + + 'mv netstat nice nl nohup nslookup open op passwd paste pathchk ping popd pr printcap ' + + 'printenv printf ps pushd pwd quota quotacheck quotactl ram rcp read readonly renice ' + + 'remsync rm rmdir rsync screen scp sdiff sed select seq set sftp shift shopt shutdown ' + + 'sleep sort source split ssh strace su sudo sum symlink sync tail tar tee test time ' + + 'times touch top traceroute trap tr true tsort tty type ulimit umask umount unalias ' + + 'uname unexpand uniq units unset unshar useradd usermod users uuencode uudecode v vdir ' + + 'vi watch wc whereis which who whoami Wget xargs yes' + ; + + this.regexList = [ + { regex: /^#!.*$/gm, css: 'preprocessor bold' }, + { regex: /\/[\w-\/]+/gm, css: 'plain' }, + { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' }, // one line comments + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // keywords + { regex: new RegExp(this.getKeywords(commands), 'gm'), css: 'functions' } // commands + ]; + } + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['bash', 'shell']; + + SyntaxHighlighter.brushes.Bash = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushCSharp.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushCSharp.js new file mode 100755 index 0000000..079214e --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushCSharp.js @@ -0,0 +1,65 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + var keywords = 'abstract as base bool break byte case catch char checked class const ' + + 'continue decimal default delegate do double else enum event explicit ' + + 'extern false finally fixed float for foreach get goto if implicit in int ' + + 'interface internal is lock long namespace new null object operator out ' + + 'override params private protected public readonly ref return sbyte sealed set ' + + 'short sizeof stackalloc static string struct switch this throw true try ' + + 'typeof uint ulong unchecked unsafe ushort using virtual void while'; + + function fixComments(match, regexInfo) + { + var css = (match[0].indexOf("///") == 0) + ? 'color1' + : 'comments' + ; + + return [new SyntaxHighlighter.Match(match[0], match.index, css)]; + } + + this.regexList = [ + { regex: SyntaxHighlighter.regexLib.singleLineCComments, func : fixComments }, // one line comments + { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments + { regex: /@"(?:[^"]|"")*"/g, css: 'string' }, // @-quoted strings + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings + { regex: /^\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // c# keyword + { regex: /\bpartial(?=\s+(?:class|interface|struct)\b)/g, css: 'keyword' }, // contextual keyword: 'partial' + { regex: /\byield(?=\s+(?:return|break)\b)/g, css: 'keyword' } // contextual keyword: 'yield' + ]; + + this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['c#', 'c-sharp', 'csharp']; + + SyntaxHighlighter.brushes.CSharp = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushColdFusion.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushColdFusion.js new file mode 100755 index 0000000..627dbb9 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushColdFusion.js @@ -0,0 +1,100 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + // Contributed by Jen + // http://www.jensbits.com/2009/05/14/coldfusion-brush-for-syntaxhighlighter-plus + + var funcs = 'Abs ACos AddSOAPRequestHeader AddSOAPResponseHeader AjaxLink AjaxOnLoad ArrayAppend ArrayAvg ArrayClear ArrayDeleteAt ' + + 'ArrayInsertAt ArrayIsDefined ArrayIsEmpty ArrayLen ArrayMax ArrayMin ArraySet ArraySort ArraySum ArraySwap ArrayToList ' + + 'Asc ASin Atn BinaryDecode BinaryEncode BitAnd BitMaskClear BitMaskRead BitMaskSet BitNot BitOr BitSHLN BitSHRN BitXor ' + + 'Ceiling CharsetDecode CharsetEncode Chr CJustify Compare CompareNoCase Cos CreateDate CreateDateTime CreateObject ' + + 'CreateODBCDate CreateODBCDateTime CreateODBCTime CreateTime CreateTimeSpan CreateUUID DateAdd DateCompare DateConvert ' + + 'DateDiff DateFormat DatePart Day DayOfWeek DayOfWeekAsString DayOfYear DaysInMonth DaysInYear DE DecimalFormat DecrementValue ' + + 'Decrypt DecryptBinary DeleteClientVariable DeserializeJSON DirectoryExists DollarFormat DotNetToCFType Duplicate Encrypt ' + + 'EncryptBinary Evaluate Exp ExpandPath FileClose FileCopy FileDelete FileExists FileIsEOF FileMove FileOpen FileRead ' + + 'FileReadBinary FileReadLine FileSetAccessMode FileSetAttribute FileSetLastModified FileWrite Find FindNoCase FindOneOf ' + + 'FirstDayOfMonth Fix FormatBaseN GenerateSecretKey GetAuthUser GetBaseTagData GetBaseTagList GetBaseTemplatePath ' + + 'GetClientVariablesList GetComponentMetaData GetContextRoot GetCurrentTemplatePath GetDirectoryFromPath GetEncoding ' + + 'GetException GetFileFromPath GetFileInfo GetFunctionList GetGatewayHelper GetHttpRequestData GetHttpTimeString ' + + 'GetK2ServerDocCount GetK2ServerDocCountLimit GetLocale GetLocaleDisplayName GetLocalHostIP GetMetaData GetMetricData ' + + 'GetPageContext GetPrinterInfo GetProfileSections GetProfileString GetReadableImageFormats GetSOAPRequest GetSOAPRequestHeader ' + + 'GetSOAPResponse GetSOAPResponseHeader GetTempDirectory GetTempFile GetTemplatePath GetTickCount GetTimeZoneInfo GetToken ' + + 'GetUserRoles GetWriteableImageFormats Hash Hour HTMLCodeFormat HTMLEditFormat IIf ImageAddBorder ImageBlur ImageClearRect ' + + 'ImageCopy ImageCrop ImageDrawArc ImageDrawBeveledRect ImageDrawCubicCurve ImageDrawLine ImageDrawLines ImageDrawOval ' + + 'ImageDrawPoint ImageDrawQuadraticCurve ImageDrawRect ImageDrawRoundRect ImageDrawText ImageFlip ImageGetBlob ImageGetBufferedImage ' + + 'ImageGetEXIFTag ImageGetHeight ImageGetIPTCTag ImageGetWidth ImageGrayscale ImageInfo ImageNegative ImageNew ImageOverlay ImagePaste ' + + 'ImageRead ImageReadBase64 ImageResize ImageRotate ImageRotateDrawingAxis ImageScaleToFit ImageSetAntialiasing ImageSetBackgroundColor ' + + 'ImageSetDrawingColor ImageSetDrawingStroke ImageSetDrawingTransparency ImageSharpen ImageShear ImageShearDrawingAxis ImageTranslate ' + + 'ImageTranslateDrawingAxis ImageWrite ImageWriteBase64 ImageXORDrawingMode IncrementValue InputBaseN Insert Int IsArray IsBinary ' + + 'IsBoolean IsCustomFunction IsDate IsDDX IsDebugMode IsDefined IsImage IsImageFile IsInstanceOf IsJSON IsLeapYear IsLocalHost ' + + 'IsNumeric IsNumericDate IsObject IsPDFFile IsPDFObject IsQuery IsSimpleValue IsSOAPRequest IsStruct IsUserInAnyRole IsUserInRole ' + + 'IsUserLoggedIn IsValid IsWDDX IsXML IsXmlAttribute IsXmlDoc IsXmlElem IsXmlNode IsXmlRoot JavaCast JSStringFormat LCase Left Len ' + + 'ListAppend ListChangeDelims ListContains ListContainsNoCase ListDeleteAt ListFind ListFindNoCase ListFirst ListGetAt ListInsertAt ' + + 'ListLast ListLen ListPrepend ListQualify ListRest ListSetAt ListSort ListToArray ListValueCount ListValueCountNoCase LJustify Log ' + + 'Log10 LSCurrencyFormat LSDateFormat LSEuroCurrencyFormat LSIsCurrency LSIsDate LSIsNumeric LSNumberFormat LSParseCurrency LSParseDateTime ' + + 'LSParseEuroCurrency LSParseNumber LSTimeFormat LTrim Max Mid Min Minute Month MonthAsString Now NumberFormat ParagraphFormat ParseDateTime ' + + 'Pi PrecisionEvaluate PreserveSingleQuotes Quarter QueryAddColumn QueryAddRow QueryConvertForGrid QueryNew QuerySetCell QuotedValueList Rand ' + + 'Randomize RandRange REFind REFindNoCase ReleaseComObject REMatch REMatchNoCase RemoveChars RepeatString Replace ReplaceList ReplaceNoCase ' + + 'REReplace REReplaceNoCase Reverse Right RJustify Round RTrim Second SendGatewayMessage SerializeJSON SetEncoding SetLocale SetProfileString ' + + 'SetVariable Sgn Sin Sleep SpanExcluding SpanIncluding Sqr StripCR StructAppend StructClear StructCopy StructCount StructDelete StructFind ' + + 'StructFindKey StructFindValue StructGet StructInsert StructIsEmpty StructKeyArray StructKeyExists StructKeyList StructKeyList StructNew ' + + 'StructSort StructUpdate Tan TimeFormat ToBase64 ToBinary ToScript ToString Trim UCase URLDecode URLEncodedFormat URLSessionFormat Val ' + + 'ValueList VerifyClient Week Wrap Wrap WriteOutput XmlChildPos XmlElemNew XmlFormat XmlGetNodeType XmlNew XmlParse XmlSearch XmlTransform ' + + 'XmlValidate Year YesNoFormat'; + + var keywords = 'cfabort cfajaximport cfajaxproxy cfapplet cfapplication cfargument cfassociate cfbreak cfcache cfcalendar ' + + 'cfcase cfcatch cfchart cfchartdata cfchartseries cfcol cfcollection cfcomponent cfcontent cfcookie cfdbinfo ' + + 'cfdefaultcase cfdirectory cfdiv cfdocument cfdocumentitem cfdocumentsection cfdump cfelse cfelseif cferror ' + + 'cfexchangecalendar cfexchangeconnection cfexchangecontact cfexchangefilter cfexchangemail cfexchangetask ' + + 'cfexecute cfexit cffeed cffile cfflush cfform cfformgroup cfformitem cfftp cffunction cfgrid cfgridcolumn ' + + 'cfgridrow cfgridupdate cfheader cfhtmlhead cfhttp cfhttpparam cfif cfimage cfimport cfinclude cfindex ' + + 'cfinput cfinsert cfinterface cfinvoke cfinvokeargument cflayout cflayoutarea cfldap cflocation cflock cflog ' + + 'cflogin cfloginuser cflogout cfloop cfmail cfmailparam cfmailpart cfmenu cfmenuitem cfmodule cfNTauthenticate ' + + 'cfobject cfobjectcache cfoutput cfparam cfpdf cfpdfform cfpdfformparam cfpdfparam cfpdfsubform cfpod cfpop ' + + 'cfpresentation cfpresentationslide cfpresenter cfprint cfprocessingdirective cfprocparam cfprocresult ' + + 'cfproperty cfquery cfqueryparam cfregistry cfreport cfreportparam cfrethrow cfreturn cfsavecontent cfschedule ' + + 'cfscript cfsearch cfselect cfset cfsetting cfsilent cfslider cfsprydataset cfstoredproc cfswitch cftable ' + + 'cftextarea cfthread cfthrow cftimer cftooltip cftrace cftransaction cftree cftreeitem cftry cfupdate cfwddx ' + + 'cfwindow cfxml cfzip cfzipparam'; + + var operators = 'all and any between cross in join like not null or outer some'; + + this.regexList = [ + { regex: new RegExp('--(.*)$', 'gm'), css: 'comments' }, // one line and multiline comments + { regex: SyntaxHighlighter.regexLib.xmlComments, css: 'comments' }, // single quoted strings + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings + { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'functions' }, // functions + { regex: new RegExp(this.getKeywords(operators), 'gmi'), css: 'color1' }, // operators and such + { regex: new RegExp(this.getKeywords(keywords), 'gmi'), css: 'keyword' } // keyword + ]; + } + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['coldfusion','cf']; + + SyntaxHighlighter.brushes.ColdFusion = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushCpp.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushCpp.js new file mode 100755 index 0000000..9f70d3a --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushCpp.js @@ -0,0 +1,97 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + // Copyright 2006 Shin, YoungJin + + var datatypes = 'ATOM BOOL BOOLEAN BYTE CHAR COLORREF DWORD DWORDLONG DWORD_PTR ' + + 'DWORD32 DWORD64 FLOAT HACCEL HALF_PTR HANDLE HBITMAP HBRUSH ' + + 'HCOLORSPACE HCONV HCONVLIST HCURSOR HDC HDDEDATA HDESK HDROP HDWP ' + + 'HENHMETAFILE HFILE HFONT HGDIOBJ HGLOBAL HHOOK HICON HINSTANCE HKEY ' + + 'HKL HLOCAL HMENU HMETAFILE HMODULE HMONITOR HPALETTE HPEN HRESULT ' + + 'HRGN HRSRC HSZ HWINSTA HWND INT INT_PTR INT32 INT64 LANGID LCID LCTYPE ' + + 'LGRPID LONG LONGLONG LONG_PTR LONG32 LONG64 LPARAM LPBOOL LPBYTE LPCOLORREF ' + + 'LPCSTR LPCTSTR LPCVOID LPCWSTR LPDWORD LPHANDLE LPINT LPLONG LPSTR LPTSTR ' + + 'LPVOID LPWORD LPWSTR LRESULT PBOOL PBOOLEAN PBYTE PCHAR PCSTR PCTSTR PCWSTR ' + + 'PDWORDLONG PDWORD_PTR PDWORD32 PDWORD64 PFLOAT PHALF_PTR PHANDLE PHKEY PINT ' + + 'PINT_PTR PINT32 PINT64 PLCID PLONG PLONGLONG PLONG_PTR PLONG32 PLONG64 POINTER_32 ' + + 'POINTER_64 PSHORT PSIZE_T PSSIZE_T PSTR PTBYTE PTCHAR PTSTR PUCHAR PUHALF_PTR ' + + 'PUINT PUINT_PTR PUINT32 PUINT64 PULONG PULONGLONG PULONG_PTR PULONG32 PULONG64 ' + + 'PUSHORT PVOID PWCHAR PWORD PWSTR SC_HANDLE SC_LOCK SERVICE_STATUS_HANDLE SHORT ' + + 'SIZE_T SSIZE_T TBYTE TCHAR UCHAR UHALF_PTR UINT UINT_PTR UINT32 UINT64 ULONG ' + + 'ULONGLONG ULONG_PTR ULONG32 ULONG64 USHORT USN VOID WCHAR WORD WPARAM WPARAM WPARAM ' + + 'char bool short int __int32 __int64 __int8 __int16 long float double __wchar_t ' + + 'clock_t _complex _dev_t _diskfree_t div_t ldiv_t _exception _EXCEPTION_POINTERS ' + + 'FILE _finddata_t _finddatai64_t _wfinddata_t _wfinddatai64_t __finddata64_t ' + + '__wfinddata64_t _FPIEEE_RECORD fpos_t _HEAPINFO _HFILE lconv intptr_t ' + + 'jmp_buf mbstate_t _off_t _onexit_t _PNH ptrdiff_t _purecall_handler ' + + 'sig_atomic_t size_t _stat __stat64 _stati64 terminate_function ' + + 'time_t __time64_t _timeb __timeb64 tm uintptr_t _utimbuf ' + + 'va_list wchar_t wctrans_t wctype_t wint_t signed'; + + var keywords = 'break case catch class const __finally __exception __try ' + + 'const_cast continue private public protected __declspec ' + + 'default delete deprecated dllexport dllimport do dynamic_cast ' + + 'else enum explicit extern if for friend goto inline ' + + 'mutable naked namespace new noinline noreturn nothrow ' + + 'register reinterpret_cast return selectany ' + + 'sizeof static static_cast struct switch template this ' + + 'thread throw true false try typedef typeid typename union ' + + 'using uuid virtual void volatile whcar_t while'; + + var functions = 'assert isalnum isalpha iscntrl isdigit isgraph islower isprint' + + 'ispunct isspace isupper isxdigit tolower toupper errno localeconv ' + + 'setlocale acos asin atan atan2 ceil cos cosh exp fabs floor fmod ' + + 'frexp ldexp log log10 modf pow sin sinh sqrt tan tanh jmp_buf ' + + 'longjmp setjmp raise signal sig_atomic_t va_arg va_end va_start ' + + 'clearerr fclose feof ferror fflush fgetc fgetpos fgets fopen ' + + 'fprintf fputc fputs fread freopen fscanf fseek fsetpos ftell ' + + 'fwrite getc getchar gets perror printf putc putchar puts remove ' + + 'rename rewind scanf setbuf setvbuf sprintf sscanf tmpfile tmpnam ' + + 'ungetc vfprintf vprintf vsprintf abort abs atexit atof atoi atol ' + + 'bsearch calloc div exit free getenv labs ldiv malloc mblen mbstowcs ' + + 'mbtowc qsort rand realloc srand strtod strtol strtoul system ' + + 'wcstombs wctomb memchr memcmp memcpy memmove memset strcat strchr ' + + 'strcmp strcoll strcpy strcspn strerror strlen strncat strncmp ' + + 'strncpy strpbrk strrchr strspn strstr strtok strxfrm asctime ' + + 'clock ctime difftime gmtime localtime mktime strftime time'; + + this.regexList = [ + { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments + { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings + { regex: /^ *#.*/gm, css: 'preprocessor' }, + { regex: new RegExp(this.getKeywords(datatypes), 'gm'), css: 'color1 bold' }, + { regex: new RegExp(this.getKeywords(functions), 'gm'), css: 'functions bold' }, + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword bold' } + ]; + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['cpp', 'c']; + + SyntaxHighlighter.brushes.Cpp = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushCss.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushCss.js new file mode 100755 index 0000000..4297a9a --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushCss.js @@ -0,0 +1,91 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + function getKeywordsCSS(str) + { + return '\\b([a-z_]|)' + str.replace(/ /g, '(?=:)\\b|\\b([a-z_\\*]|\\*|)') + '(?=:)\\b'; + }; + + function getValuesCSS(str) + { + return '\\b' + str.replace(/ /g, '(?!-)(?!:)\\b|\\b()') + '\:\\b'; + }; + + var keywords = 'ascent azimuth background-attachment background-color background-image background-position ' + + 'background-repeat background baseline bbox border-collapse border-color border-spacing border-style border-top ' + + 'border-right border-bottom border-left border-top-color border-right-color border-bottom-color border-left-color ' + + 'border-top-style border-right-style border-bottom-style border-left-style border-top-width border-right-width ' + + 'border-bottom-width border-left-width border-width border bottom cap-height caption-side centerline clear clip color ' + + 'content counter-increment counter-reset cue-after cue-before cue cursor definition-src descent direction display ' + + 'elevation empty-cells float font-size-adjust font-family font-size font-stretch font-style font-variant font-weight font ' + + 'height left letter-spacing line-height list-style-image list-style-position list-style-type list-style margin-top ' + + 'margin-right margin-bottom margin-left margin marker-offset marks mathline max-height max-width min-height min-width orphans ' + + 'outline-color outline-style outline-width outline overflow padding-top padding-right padding-bottom padding-left padding page ' + + 'page-break-after page-break-before page-break-inside pause pause-after pause-before pitch pitch-range play-during position ' + + 'quotes right richness size slope src speak-header speak-numeral speak-punctuation speak speech-rate stemh stemv stress ' + + 'table-layout text-align top text-decoration text-indent text-shadow text-transform unicode-bidi unicode-range units-per-em ' + + 'vertical-align visibility voice-family volume white-space widows width widths word-spacing x-height z-index'; + + var values = 'above absolute all always aqua armenian attr aural auto avoid baseline behind below bidi-override black blink block blue bold bolder '+ + 'both bottom braille capitalize caption center center-left center-right circle close-quote code collapse compact condensed '+ + 'continuous counter counters crop cross crosshair cursive dashed decimal decimal-leading-zero default digits disc dotted double '+ + 'embed embossed e-resize expanded extra-condensed extra-expanded fantasy far-left far-right fast faster fixed format fuchsia '+ + 'gray green groove handheld hebrew help hidden hide high higher icon inline-table inline inset inside invert italic '+ + 'justify landscape large larger left-side left leftwards level lighter lime line-through list-item local loud lower-alpha '+ + 'lowercase lower-greek lower-latin lower-roman lower low ltr marker maroon medium message-box middle mix move narrower '+ + 'navy ne-resize no-close-quote none no-open-quote no-repeat normal nowrap n-resize nw-resize oblique olive once open-quote outset '+ + 'outside overline pointer portrait pre print projection purple red relative repeat repeat-x repeat-y rgb ridge right right-side '+ + 'rightwards rtl run-in screen scroll semi-condensed semi-expanded separate se-resize show silent silver slower slow '+ + 'small small-caps small-caption smaller soft solid speech spell-out square s-resize static status-bar sub super sw-resize '+ + 'table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group teal '+ + 'text-bottom text-top thick thin top transparent tty tv ultra-condensed ultra-expanded underline upper-alpha uppercase upper-latin '+ + 'upper-roman url visible wait white wider w-resize x-fast x-high x-large x-loud x-low x-slow x-small x-soft xx-large xx-small yellow'; + + var fonts = '[mM]onospace [tT]ahoma [vV]erdana [aA]rial [hH]elvetica [sS]ans-serif [sS]erif [cC]ourier mono sans serif'; + + this.regexList = [ + { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings + { regex: /\#[a-fA-F0-9]{3,6}/g, css: 'value' }, // html colors + { regex: /(-?\d+)(\.\d+)?(px|em|pt|\:|\%|)/g, css: 'value' }, // sizes + { regex: /!important/g, css: 'color3' }, // !important + { regex: new RegExp(getKeywordsCSS(keywords), 'gm'), css: 'keyword' }, // keywords + { regex: new RegExp(getValuesCSS(values), 'g'), css: 'value' }, // values + { regex: new RegExp(this.getKeywords(fonts), 'g'), css: 'color1' } // fonts + ]; + + this.forHtmlScript({ + left: /(<|<)\s*style.*?(>|>)/gi, + right: /(<|<)\/\s*style\s*(>|>)/gi + }); + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['css']; + + SyntaxHighlighter.brushes.CSS = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushDelphi.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushDelphi.js new file mode 100755 index 0000000..e1060d4 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushDelphi.js @@ -0,0 +1,55 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + var keywords = 'abs addr and ansichar ansistring array as asm begin boolean byte cardinal ' + + 'case char class comp const constructor currency destructor div do double ' + + 'downto else end except exports extended false file finalization finally ' + + 'for function goto if implementation in inherited int64 initialization ' + + 'integer interface is label library longint longword mod nil not object ' + + 'of on or packed pansichar pansistring pchar pcurrency pdatetime pextended ' + + 'pint64 pointer private procedure program property pshortstring pstring ' + + 'pvariant pwidechar pwidestring protected public published raise real real48 ' + + 'record repeat set shl shortint shortstring shr single smallint string then ' + + 'threadvar to true try type unit until uses val var varirnt while widechar ' + + 'widestring with word write writeln xor'; + + this.regexList = [ + { regex: /\(\*[\s\S]*?\*\)/gm, css: 'comments' }, // multiline comments (* *) + { regex: /{(?!\$)[\s\S]*?}/gm, css: 'comments' }, // multiline comments { } + { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings + { regex: /\{\$[a-zA-Z]+ .+\}/g, css: 'color1' }, // compiler Directives and Region tags + { regex: /\b[\d\.]+\b/g, css: 'value' }, // numbers 12345 + { regex: /\$[a-zA-Z0-9]+\b/g, css: 'value' }, // numbers $F5D3 + { regex: new RegExp(this.getKeywords(keywords), 'gmi'), css: 'keyword' } // keyword + ]; + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['delphi', 'pascal', 'pas']; + + SyntaxHighlighter.brushes.Delphi = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushDiff.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushDiff.js new file mode 100755 index 0000000..e9b14fc --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushDiff.js @@ -0,0 +1,41 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + this.regexList = [ + { regex: /^\+\+\+.*$/gm, css: 'color2' }, + { regex: /^\-\-\-.*$/gm, css: 'color2' }, + { regex: /^\s.*$/gm, css: 'color1' }, + { regex: /^@@.*@@$/gm, css: 'variable' }, + { regex: /^\+[^\+]{1}.*$/gm, css: 'string' }, + { regex: /^\-[^\-]{1}.*$/gm, css: 'comments' } + ]; + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['diff', 'patch']; + + SyntaxHighlighter.brushes.Diff = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushErlang.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushErlang.js new file mode 100755 index 0000000..6ba7d9d --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushErlang.js @@ -0,0 +1,52 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + // Contributed by Jean-Lou Dupont + // http://jldupont.blogspot.com/2009/06/erlang-syntax-highlighter.html + + // According to: http://erlang.org/doc/reference_manual/introduction.html#1.5 + var keywords = 'after and andalso band begin bnot bor bsl bsr bxor '+ + 'case catch cond div end fun if let not of or orelse '+ + 'query receive rem try when xor'+ + // additional + ' module export import define'; + + this.regexList = [ + { regex: new RegExp("[A-Z][A-Za-z0-9_]+", 'g'), css: 'constants' }, + { regex: new RegExp("\\%.+", 'gm'), css: 'comments' }, + { regex: new RegExp("\\?[A-Za-z0-9_]+", 'g'), css: 'preprocessor' }, + { regex: new RegExp("[a-z0-9_]+:[a-z0-9_]+", 'g'), css: 'functions' }, + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } + ]; + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['erl', 'erlang']; + + SyntaxHighlighter.brushes.Erland = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushGroovy.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushGroovy.js new file mode 100755 index 0000000..6ec5c18 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushGroovy.js @@ -0,0 +1,67 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + // Contributed by Andres Almiray + // http://jroller.com/aalmiray/entry/nice_source_code_syntax_highlighter + + var keywords = 'as assert break case catch class continue def default do else extends finally ' + + 'if in implements import instanceof interface new package property return switch ' + + 'throw throws try while public protected private static'; + var types = 'void boolean byte char short int long float double'; + var constants = 'null'; + var methods = 'allProperties count get size '+ + 'collect each eachProperty eachPropertyName eachWithIndex find findAll ' + + 'findIndexOf grep inject max min reverseEach sort ' + + 'asImmutable asSynchronized flatten intersect join pop reverse subMap toList ' + + 'padRight padLeft contains eachMatch toCharacter toLong toUrl tokenize ' + + 'eachFile eachFileRecurse eachB yte eachLine readBytes readLine getText ' + + 'splitEachLine withReader append encodeBase64 decodeBase64 filterLine ' + + 'transformChar transformLine withOutputStream withPrintWriter withStream ' + + 'withStreams withWriter withWriterAppend write writeLine '+ + 'dump inspect invokeMethod print println step times upto use waitForOrKill '+ + 'getText'; + + this.regexList = [ + { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments + { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings + { regex: /""".*"""/g, css: 'string' }, // GStrings + { regex: new RegExp('\\b([\\d]+(\\.[\\d]+)?|0x[a-f0-9]+)\\b', 'gi'), css: 'value' }, // numbers + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // goovy keyword + { regex: new RegExp(this.getKeywords(types), 'gm'), css: 'color1' }, // goovy/java type + { regex: new RegExp(this.getKeywords(constants), 'gm'), css: 'constants' }, // constants + { regex: new RegExp(this.getKeywords(methods), 'gm'), css: 'functions' } // methods + ]; + + this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); + } + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['groovy']; + + SyntaxHighlighter.brushes.Groovy = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushJScript.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushJScript.js new file mode 100755 index 0000000..ff98dab --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushJScript.js @@ -0,0 +1,52 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + var keywords = 'break case catch continue ' + + 'default delete do else false ' + + 'for function if in instanceof ' + + 'new null return super switch ' + + 'this throw true try typeof var while with' + ; + + var r = SyntaxHighlighter.regexLib; + + this.regexList = [ + { regex: r.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings + { regex: r.multiLineSingleQuotedString, css: 'string' }, // single quoted strings + { regex: r.singleLineCComments, css: 'comments' }, // one line comments + { regex: r.multiLineCComments, css: 'comments' }, // multiline comments + { regex: /\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // keywords + ]; + + this.forHtmlScript(r.scriptScriptTags); + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['js', 'jscript', 'javascript']; + + SyntaxHighlighter.brushes.JScript = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushJava.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushJava.js new file mode 100755 index 0000000..d692fd6 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushJava.js @@ -0,0 +1,57 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + var keywords = 'abstract assert boolean break byte case catch char class const ' + + 'continue default do double else enum extends ' + + 'false final finally float for goto if implements import ' + + 'instanceof int interface long native new null ' + + 'package private protected public return ' + + 'short static strictfp super switch synchronized this throw throws true ' + + 'transient try void volatile while'; + + this.regexList = [ + { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments + { regex: /\/\*([^\*][\s\S]*)?\*\//gm, css: 'comments' }, // multiline comments + { regex: /\/\*(?!\*\/)\*[\s\S]*?\*\//gm, css: 'preprocessor' }, // documentation comments + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings + { regex: /\b([\d]+(\.[\d]+)?|0x[a-f0-9]+)\b/gi, css: 'value' }, // numbers + { regex: /(?!\@interface\b)\@[\$\w]+\b/g, css: 'color1' }, // annotation @anno + { regex: /\@interface\b/g, css: 'color2' }, // @interface keyword + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // java keyword + ]; + + this.forHtmlScript({ + left : /(<|<)%[@!=]?/g, + right : /%(>|>)/g + }); + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['java']; + + SyntaxHighlighter.brushes.Java = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushJavaFX.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushJavaFX.js new file mode 100755 index 0000000..1a150a6 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushJavaFX.js @@ -0,0 +1,58 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + // Contributed by Patrick Webster + // http://patrickwebster.blogspot.com/2009/04/javafx-brush-for-syntaxhighlighter.html + var datatypes = 'Boolean Byte Character Double Duration ' + + 'Float Integer Long Number Short String Void' + ; + + var keywords = 'abstract after and as assert at before bind bound break catch class ' + + 'continue def delete else exclusive extends false finally first for from ' + + 'function if import in indexof init insert instanceof into inverse last ' + + 'lazy mixin mod nativearray new not null on or override package postinit ' + + 'protected public public-init public-read replace return reverse sizeof ' + + 'step super then this throw true try tween typeof var where while with ' + + 'attribute let private readonly static trigger' + ; + + this.regexList = [ + { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, + { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, + { regex: /(-?\.?)(\b(\d*\.?\d+|\d+\.?\d*)(e[+-]?\d+)?|0x[a-f\d]+)\b\.?/gi, css: 'color2' }, // numbers + { regex: new RegExp(this.getKeywords(datatypes), 'gm'), css: 'variable' }, // datatypes + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } + ]; + this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['jfx', 'javafx']; + + SyntaxHighlighter.brushes.JavaFX = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPerl.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPerl.js new file mode 100755 index 0000000..d94a2e0 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPerl.js @@ -0,0 +1,72 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + // Contributed by David Simmons-Duffin and Marty Kube + + var funcs = + 'abs accept alarm atan2 bind binmode chdir chmod chomp chop chown chr ' + + 'chroot close closedir connect cos crypt defined delete each endgrent ' + + 'endhostent endnetent endprotoent endpwent endservent eof exec exists ' + + 'exp fcntl fileno flock fork format formline getc getgrent getgrgid ' + + 'getgrnam gethostbyaddr gethostbyname gethostent getlogin getnetbyaddr ' + + 'getnetbyname getnetent getpeername getpgrp getppid getpriority ' + + 'getprotobyname getprotobynumber getprotoent getpwent getpwnam getpwuid ' + + 'getservbyname getservbyport getservent getsockname getsockopt glob ' + + 'gmtime grep hex index int ioctl join keys kill lc lcfirst length link ' + + 'listen localtime lock log lstat map mkdir msgctl msgget msgrcv msgsnd ' + + 'oct open opendir ord pack pipe pop pos print printf prototype push ' + + 'quotemeta rand read readdir readline readlink readpipe recv rename ' + + 'reset reverse rewinddir rindex rmdir scalar seek seekdir select semctl ' + + 'semget semop send setgrent sethostent setnetent setpgrp setpriority ' + + 'setprotoent setpwent setservent setsockopt shift shmctl shmget shmread ' + + 'shmwrite shutdown sin sleep socket socketpair sort splice split sprintf ' + + 'sqrt srand stat study substr symlink syscall sysopen sysread sysseek ' + + 'system syswrite tell telldir time times tr truncate uc ucfirst umask ' + + 'undef unlink unpack unshift utime values vec wait waitpid warn write'; + + var keywords = + 'bless caller continue dbmclose dbmopen die do dump else elsif eval exit ' + + 'for foreach goto if import last local my next no our package redo ref ' + + 'require return sub tie tied unless untie until use wantarray while'; + + this.regexList = [ + { regex: new RegExp('#[^!].*$', 'gm'), css: 'comments' }, + { regex: new RegExp('^\\s*#!.*$', 'gm'), css: 'preprocessor' }, // shebang + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, + { regex: new RegExp('(\\$|@|%)\\w+', 'g'), css: 'variable' }, + { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'functions' }, + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } + ]; + + this.forHtmlScript(SyntaxHighlighter.regexLib.phpScriptTags); + } + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['perl', 'Perl', 'pl']; + + SyntaxHighlighter.brushes.Perl = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPhp.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPhp.js new file mode 100755 index 0000000..95e6e43 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPhp.js @@ -0,0 +1,88 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + var funcs = 'abs acos acosh addcslashes addslashes ' + + 'array_change_key_case array_chunk array_combine array_count_values array_diff '+ + 'array_diff_assoc array_diff_key array_diff_uassoc array_diff_ukey array_fill '+ + 'array_filter array_flip array_intersect array_intersect_assoc array_intersect_key '+ + 'array_intersect_uassoc array_intersect_ukey array_key_exists array_keys array_map '+ + 'array_merge array_merge_recursive array_multisort array_pad array_pop array_product '+ + 'array_push array_rand array_reduce array_reverse array_search array_shift '+ + 'array_slice array_splice array_sum array_udiff array_udiff_assoc '+ + 'array_udiff_uassoc array_uintersect array_uintersect_assoc '+ + 'array_uintersect_uassoc array_unique array_unshift array_values array_walk '+ + 'array_walk_recursive atan atan2 atanh base64_decode base64_encode base_convert '+ + 'basename bcadd bccomp bcdiv bcmod bcmul bindec bindtextdomain bzclose bzcompress '+ + 'bzdecompress bzerrno bzerror bzerrstr bzflush bzopen bzread bzwrite ceil chdir '+ + 'checkdate checkdnsrr chgrp chmod chop chown chr chroot chunk_split class_exists '+ + 'closedir closelog copy cos cosh count count_chars date decbin dechex decoct '+ + 'deg2rad delete ebcdic2ascii echo empty end ereg ereg_replace eregi eregi_replace error_log '+ + 'error_reporting escapeshellarg escapeshellcmd eval exec exit exp explode extension_loaded '+ + 'feof fflush fgetc fgetcsv fgets fgetss file_exists file_get_contents file_put_contents '+ + 'fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype '+ + 'floatval flock floor flush fmod fnmatch fopen fpassthru fprintf fputcsv fputs fread fscanf '+ + 'fseek fsockopen fstat ftell ftok getallheaders getcwd getdate getenv gethostbyaddr gethostbyname '+ + 'gethostbynamel getimagesize getlastmod getmxrr getmygid getmyinode getmypid getmyuid getopt '+ + 'getprotobyname getprotobynumber getrandmax getrusage getservbyname getservbyport gettext '+ + 'gettimeofday gettype glob gmdate gmmktime ini_alter ini_get ini_get_all ini_restore ini_set '+ + 'interface_exists intval ip2long is_a is_array is_bool is_callable is_dir is_double '+ + 'is_executable is_file is_finite is_float is_infinite is_int is_integer is_link is_long '+ + 'is_nan is_null is_numeric is_object is_readable is_real is_resource is_scalar is_soap_fault '+ + 'is_string is_subclass_of is_uploaded_file is_writable is_writeable mkdir mktime nl2br '+ + 'parse_ini_file parse_str parse_url passthru pathinfo print readlink realpath rewind rewinddir rmdir '+ + 'round str_ireplace str_pad str_repeat str_replace str_rot13 str_shuffle str_split '+ + 'str_word_count strcasecmp strchr strcmp strcoll strcspn strftime strip_tags stripcslashes '+ + 'stripos stripslashes stristr strlen strnatcasecmp strnatcmp strncasecmp strncmp strpbrk '+ + 'strpos strptime strrchr strrev strripos strrpos strspn strstr strtok strtolower strtotime '+ + 'strtoupper strtr strval substr substr_compare'; + + var keywords = 'abstract and array as break case catch cfunction class clone const continue declare default die do ' + + 'else elseif enddeclare endfor endforeach endif endswitch endwhile extends final for foreach ' + + 'function include include_once global goto if implements interface instanceof namespace new ' + + 'old_function or private protected public return require require_once static switch ' + + 'throw try use var while xor '; + + var constants = '__FILE__ __LINE__ __METHOD__ __FUNCTION__ __CLASS__'; + + this.regexList = [ + { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments + { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings + { regex: /\$\w+/g, css: 'variable' }, // variables + { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'functions' }, // common functions + { regex: new RegExp(this.getKeywords(constants), 'gmi'), css: 'constants' }, // constants + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // keyword + ]; + + this.forHtmlScript(SyntaxHighlighter.regexLib.phpScriptTags); + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['php']; + + SyntaxHighlighter.brushes.Php = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPlain.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPlain.js new file mode 100755 index 0000000..9f7d9e9 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPlain.js @@ -0,0 +1,33 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['text', 'plain']; + + SyntaxHighlighter.brushes.Plain = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPowerShell.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPowerShell.js new file mode 100755 index 0000000..0be1752 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPowerShell.js @@ -0,0 +1,74 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + // Contributes by B.v.Zanten, Getronics + // http://confluence.atlassian.com/display/CONFEXT/New+Code+Macro + + var keywords = 'Add-Content Add-History Add-Member Add-PSSnapin Clear(-Content)? Clear-Item ' + + 'Clear-ItemProperty Clear-Variable Compare-Object ConvertFrom-SecureString Convert-Path ' + + 'ConvertTo-Html ConvertTo-SecureString Copy(-Item)? Copy-ItemProperty Export-Alias ' + + 'Export-Clixml Export-Console Export-Csv ForEach(-Object)? Format-Custom Format-List ' + + 'Format-Table Format-Wide Get-Acl Get-Alias Get-AuthenticodeSignature Get-ChildItem Get-Command ' + + 'Get-Content Get-Credential Get-Culture Get-Date Get-EventLog Get-ExecutionPolicy ' + + 'Get-Help Get-History Get-Host Get-Item Get-ItemProperty Get-Location Get-Member ' + + 'Get-PfxCertificate Get-Process Get-PSDrive Get-PSProvider Get-PSSnapin Get-Service ' + + 'Get-TraceSource Get-UICulture Get-Unique Get-Variable Get-WmiObject Group-Object ' + + 'Import-Alias Import-Clixml Import-Csv Invoke-Expression Invoke-History Invoke-Item ' + + 'Join-Path Measure-Command Measure-Object Move(-Item)? Move-ItemProperty New-Alias ' + + 'New-Item New-ItemProperty New-Object New-PSDrive New-Service New-TimeSpan ' + + 'New-Variable Out-Default Out-File Out-Host Out-Null Out-Printer Out-String Pop-Location ' + + 'Push-Location Read-Host Remove-Item Remove-ItemProperty Remove-PSDrive Remove-PSSnapin ' + + 'Remove-Variable Rename-Item Rename-ItemProperty Resolve-Path Restart-Service Resume-Service ' + + 'Select-Object Select-String Set-Acl Set-Alias Set-AuthenticodeSignature Set-Content ' + + 'Set-Date Set-ExecutionPolicy Set-Item Set-ItemProperty Set-Location Set-PSDebug ' + + 'Set-Service Set-TraceSource Set(-Variable)? Sort-Object Split-Path Start-Service ' + + 'Start-Sleep Start-Transcript Stop-Process Stop-Service Stop-Transcript Suspend-Service ' + + 'Tee-Object Test-Path Trace-Command Update-FormatData Update-TypeData Where(-Object)? ' + + 'Write-Debug Write-Error Write(-Host)? Write-Output Write-Progress Write-Verbose Write-Warning'; + var alias = 'ac asnp clc cli clp clv cpi cpp cvpa diff epal epcsv fc fl ' + + 'ft fw gal gc gci gcm gdr ghy gi gl gm gp gps group gsv ' + + 'gsnp gu gv gwmi iex ihy ii ipal ipcsv mi mp nal ndr ni nv oh rdr ' + + 'ri rni rnp rp rsnp rv rvpa sal sasv sc select si sl sleep sort sp ' + + 'spps spsv sv tee cat cd cp h history kill lp ls ' + + 'mount mv popd ps pushd pwd r rm rmdir echo cls chdir del dir ' + + 'erase rd ren type % \\?'; + + this.regexList = [ + { regex: /#.*$/gm, css: 'comments' }, // one line comments + { regex: /\$[a-zA-Z0-9]+\b/g, css: 'value' }, // variables $Computer1 + { regex: /\-[a-zA-Z]+\b/g, css: 'keyword' }, // Operators -not -and -eq + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings + { regex: new RegExp(this.getKeywords(keywords), 'gmi'), css: 'keyword' }, + { regex: new RegExp(this.getKeywords(alias), 'gmi'), css: 'keyword' } + ]; + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['powershell', 'ps']; + + SyntaxHighlighter.brushes.PowerShell = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPython.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPython.js new file mode 100755 index 0000000..ce77462 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushPython.js @@ -0,0 +1,64 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + // Contributed by Gheorghe Milas and Ahmad Sherif + + var keywords = 'and assert break class continue def del elif else ' + + 'except exec finally for from global if import in is ' + + 'lambda not or pass print raise return try yield while'; + + var funcs = '__import__ abs all any apply basestring bin bool buffer callable ' + + 'chr classmethod cmp coerce compile complex delattr dict dir ' + + 'divmod enumerate eval execfile file filter float format frozenset ' + + 'getattr globals hasattr hash help hex id input int intern ' + + 'isinstance issubclass iter len list locals long map max min next ' + + 'object oct open ord pow print property range raw_input reduce ' + + 'reload repr reversed round set setattr slice sorted staticmethod ' + + 'str sum super tuple type type unichr unicode vars xrange zip'; + + var special = 'None True False self cls class_'; + + this.regexList = [ + { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' }, + { regex: /^\s*@\w+/gm, css: 'decorator' }, + { regex: /(['\"]{3})([^\1])*?\1/gm, css: 'comments' }, + { regex: /"(?!")(?:\.|\\\"|[^\""\n])*"/gm, css: 'string' }, + { regex: /'(?!')(?:\.|(\\\')|[^\''\n])*'/gm, css: 'string' }, + { regex: /\+|\-|\*|\/|\%|=|==/gm, css: 'keyword' }, + { regex: /\b\d+\.?\w*/g, css: 'value' }, + { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'functions' }, + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, + { regex: new RegExp(this.getKeywords(special), 'gm'), css: 'color1' } + ]; + + this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['py', 'python']; + + SyntaxHighlighter.brushes.Python = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushRuby.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushRuby.js new file mode 100755 index 0000000..ff82130 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushRuby.js @@ -0,0 +1,55 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + // Contributed by Erik Peterson. + + var keywords = 'alias and BEGIN begin break case class def define_method defined do each else elsif ' + + 'END end ensure false for if in module new next nil not or raise redo rescue retry return ' + + 'self super then throw true undef unless until when while yield'; + + var builtins = 'Array Bignum Binding Class Continuation Dir Exception FalseClass File::Stat File Fixnum Fload ' + + 'Hash Integer IO MatchData Method Module NilClass Numeric Object Proc Range Regexp String Struct::TMS Symbol ' + + 'ThreadGroup Thread Time TrueClass'; + + this.regexList = [ + { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' }, // one line comments + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings + { regex: /\b[A-Z0-9_]+\b/g, css: 'constants' }, // constants + { regex: /:[a-z][A-Za-z0-9_]*/g, css: 'color2' }, // symbols + { regex: /(\$|@@|@)\w+/g, css: 'variable bold' }, // $global, @instance, and @@class variables + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // keywords + { regex: new RegExp(this.getKeywords(builtins), 'gm'), css: 'color1' } // builtins + ]; + + this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['ruby', 'rails', 'ror', 'rb']; + + SyntaxHighlighter.brushes.Ruby = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushSass.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushSass.js new file mode 100755 index 0000000..aa04da0 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushSass.js @@ -0,0 +1,94 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + function getKeywordsCSS(str) + { + return '\\b([a-z_]|)' + str.replace(/ /g, '(?=:)\\b|\\b([a-z_\\*]|\\*|)') + '(?=:)\\b'; + }; + + function getValuesCSS(str) + { + return '\\b' + str.replace(/ /g, '(?!-)(?!:)\\b|\\b()') + '\:\\b'; + }; + + var keywords = 'ascent azimuth background-attachment background-color background-image background-position ' + + 'background-repeat background baseline bbox border-collapse border-color border-spacing border-style border-top ' + + 'border-right border-bottom border-left border-top-color border-right-color border-bottom-color border-left-color ' + + 'border-top-style border-right-style border-bottom-style border-left-style border-top-width border-right-width ' + + 'border-bottom-width border-left-width border-width border bottom cap-height caption-side centerline clear clip color ' + + 'content counter-increment counter-reset cue-after cue-before cue cursor definition-src descent direction display ' + + 'elevation empty-cells float font-size-adjust font-family font-size font-stretch font-style font-variant font-weight font ' + + 'height left letter-spacing line-height list-style-image list-style-position list-style-type list-style margin-top ' + + 'margin-right margin-bottom margin-left margin marker-offset marks mathline max-height max-width min-height min-width orphans ' + + 'outline-color outline-style outline-width outline overflow padding-top padding-right padding-bottom padding-left padding page ' + + 'page-break-after page-break-before page-break-inside pause pause-after pause-before pitch pitch-range play-during position ' + + 'quotes right richness size slope src speak-header speak-numeral speak-punctuation speak speech-rate stemh stemv stress ' + + 'table-layout text-align top text-decoration text-indent text-shadow text-transform unicode-bidi unicode-range units-per-em ' + + 'vertical-align visibility voice-family volume white-space widows width widths word-spacing x-height z-index'; + + var values = 'above absolute all always aqua armenian attr aural auto avoid baseline behind below bidi-override black blink block blue bold bolder '+ + 'both bottom braille capitalize caption center center-left center-right circle close-quote code collapse compact condensed '+ + 'continuous counter counters crop cross crosshair cursive dashed decimal decimal-leading-zero digits disc dotted double '+ + 'embed embossed e-resize expanded extra-condensed extra-expanded fantasy far-left far-right fast faster fixed format fuchsia '+ + 'gray green groove handheld hebrew help hidden hide high higher icon inline-table inline inset inside invert italic '+ + 'justify landscape large larger left-side left leftwards level lighter lime line-through list-item local loud lower-alpha '+ + 'lowercase lower-greek lower-latin lower-roman lower low ltr marker maroon medium message-box middle mix move narrower '+ + 'navy ne-resize no-close-quote none no-open-quote no-repeat normal nowrap n-resize nw-resize oblique olive once open-quote outset '+ + 'outside overline pointer portrait pre print projection purple red relative repeat repeat-x repeat-y rgb ridge right right-side '+ + 'rightwards rtl run-in screen scroll semi-condensed semi-expanded separate se-resize show silent silver slower slow '+ + 'small small-caps small-caption smaller soft solid speech spell-out square s-resize static status-bar sub super sw-resize '+ + 'table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group teal '+ + 'text-bottom text-top thick thin top transparent tty tv ultra-condensed ultra-expanded underline upper-alpha uppercase upper-latin '+ + 'upper-roman url visible wait white wider w-resize x-fast x-high x-large x-loud x-low x-slow x-small x-soft xx-large xx-small yellow'; + + var fonts = '[mM]onospace [tT]ahoma [vV]erdana [aA]rial [hH]elvetica [sS]ans-serif [sS]erif [cC]ourier mono sans serif'; + + var statements = '!important !default'; + var preprocessor = '@import @extend @debug @warn @if @for @while @mixin @include'; + + var r = SyntaxHighlighter.regexLib; + + this.regexList = [ + { regex: r.multiLineCComments, css: 'comments' }, // multiline comments + { regex: r.singleLineCComments, css: 'comments' }, // singleline comments + { regex: r.doubleQuotedString, css: 'string' }, // double quoted strings + { regex: r.singleQuotedString, css: 'string' }, // single quoted strings + { regex: /\#[a-fA-F0-9]{3,6}/g, css: 'value' }, // html colors + { regex: /\b(-?\d+)(\.\d+)?(px|em|pt|\:|\%|)\b/g, css: 'value' }, // sizes + { regex: /\$\w+/g, css: 'variable' }, // variables + { regex: new RegExp(this.getKeywords(statements), 'g'), css: 'color3' }, // statements + { regex: new RegExp(this.getKeywords(preprocessor), 'g'), css: 'preprocessor' }, // preprocessor + { regex: new RegExp(getKeywordsCSS(keywords), 'gm'), css: 'keyword' }, // keywords + { regex: new RegExp(getValuesCSS(values), 'g'), css: 'value' }, // values + { regex: new RegExp(this.getKeywords(fonts), 'g'), css: 'color1' } // fonts + ]; + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['sass', 'scss']; + + SyntaxHighlighter.brushes.Sass = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushScala.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushScala.js new file mode 100755 index 0000000..4b0b6f0 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushScala.js @@ -0,0 +1,51 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + // Contributed by Yegor Jbanov and David Bernard. + + var keywords = 'val sealed case def true trait implicit forSome import match object null finally super ' + + 'override try lazy for var catch throw type extends class while with new final yield abstract ' + + 'else do if return protected private this package false'; + + var keyops = '[_:=><%#@]+'; + + this.regexList = [ + { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments + { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments + { regex: SyntaxHighlighter.regexLib.multiLineSingleQuotedString, css: 'string' }, // multi-line strings + { regex: SyntaxHighlighter.regexLib.multiLineDoubleQuotedString, css: 'string' }, // double-quoted string + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings + { regex: /0x[a-f0-9]+|\d+(\.\d+)?/gi, css: 'value' }, // numbers + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // keywords + { regex: new RegExp(keyops, 'gm'), css: 'keyword' } // scala keyword + ]; + } + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['scala']; + + SyntaxHighlighter.brushes.Scala = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushSql.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushSql.js new file mode 100755 index 0000000..5c2cd88 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushSql.js @@ -0,0 +1,66 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + var funcs = 'abs avg case cast coalesce convert count current_timestamp ' + + 'current_user day isnull left lower month nullif replace right ' + + 'session_user space substring sum system_user upper user year'; + + var keywords = 'absolute action add after alter as asc at authorization begin bigint ' + + 'binary bit by cascade char character check checkpoint close collate ' + + 'column commit committed connect connection constraint contains continue ' + + 'create cube current current_date current_time cursor database date ' + + 'deallocate dec decimal declare default delete desc distinct double drop ' + + 'dynamic else end end-exec escape except exec execute false fetch first ' + + 'float for force foreign forward free from full function global goto grant ' + + 'group grouping having hour ignore index inner insensitive insert instead ' + + 'int integer intersect into is isolation key last level load local max min ' + + 'minute modify move name national nchar next no numeric of off on only ' + + 'open option order out output partial password precision prepare primary ' + + 'prior privileges procedure public read real references relative repeatable ' + + 'restrict return returns revoke rollback rollup rows rule schema scroll ' + + 'second section select sequence serializable set size smallint static ' + + 'statistics table temp temporary then time timestamp to top transaction ' + + 'translation trigger true truncate uncommitted union unique update values ' + + 'varchar varying view when where with work'; + + var operators = 'all and any between cross in join like not null or outer some'; + + this.regexList = [ + { regex: /--(.*)$/gm, css: 'comments' }, // one line and multiline comments + { regex: SyntaxHighlighter.regexLib.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings + { regex: SyntaxHighlighter.regexLib.multiLineSingleQuotedString, css: 'string' }, // single quoted strings + { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'color2' }, // functions + { regex: new RegExp(this.getKeywords(operators), 'gmi'), css: 'color1' }, // operators and such + { regex: new RegExp(this.getKeywords(keywords), 'gmi'), css: 'keyword' } // keyword + ]; + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['sql']; + + SyntaxHighlighter.brushes.Sql = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushVb.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushVb.js new file mode 100755 index 0000000..be845dc --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushVb.js @@ -0,0 +1,56 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + var keywords = 'AddHandler AddressOf AndAlso Alias And Ansi As Assembly Auto ' + + 'Boolean ByRef Byte ByVal Call Case Catch CBool CByte CChar CDate ' + + 'CDec CDbl Char CInt Class CLng CObj Const CShort CSng CStr CType ' + + 'Date Decimal Declare Default Delegate Dim DirectCast Do Double Each ' + + 'Else ElseIf End Enum Erase Error Event Exit False Finally For Friend ' + + 'Function Get GetType GoSub GoTo Handles If Implements Imports In ' + + 'Inherits Integer Interface Is Let Lib Like Long Loop Me Mod Module ' + + 'MustInherit MustOverride MyBase MyClass Namespace New Next Not Nothing ' + + 'NotInheritable NotOverridable Object On Option Optional Or OrElse ' + + 'Overloads Overridable Overrides ParamArray Preserve Private Property ' + + 'Protected Public RaiseEvent ReadOnly ReDim REM RemoveHandler Resume ' + + 'Return Select Set Shadows Shared Short Single Static Step Stop String ' + + 'Structure Sub SyncLock Then Throw To True Try TypeOf Unicode Until ' + + 'Variant When While With WithEvents WriteOnly Xor'; + + this.regexList = [ + { regex: /'.*$/gm, css: 'comments' }, // one line comments + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings + { regex: /^\s*#.*$/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // vb keyword + ]; + + this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['vb', 'vbnet']; + + SyntaxHighlighter.brushes.Vb = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushXml.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushXml.js new file mode 100755 index 0000000..69d9fd0 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shBrushXml.js @@ -0,0 +1,69 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + function process(match, regexInfo) + { + var constructor = SyntaxHighlighter.Match, + code = match[0], + tag = new XRegExp('(<|<)[\\s\\/\\?]*(?[:\\w-\\.]+)', 'xg').exec(code), + result = [] + ; + + if (match.attributes != null) + { + var attributes, + regex = new XRegExp('(? [\\w:\\-\\.]+)' + + '\\s*=\\s*' + + '(? ".*?"|\'.*?\'|\\w+)', + 'xg'); + + while ((attributes = regex.exec(code)) != null) + { + result.push(new constructor(attributes.name, match.index + attributes.index, 'color1')); + result.push(new constructor(attributes.value, match.index + attributes.index + attributes[0].indexOf(attributes.value), 'string')); + } + } + + if (tag != null) + result.push( + new constructor(tag.name, match.index + tag[0].indexOf(tag.name), 'keyword') + ); + + return result; + } + + this.regexList = [ + { regex: new XRegExp('(\\<|<)\\!\\[[\\w\\s]*?\\[(.|\\s)*?\\]\\](\\>|>)', 'gm'), css: 'color2' }, // + { regex: SyntaxHighlighter.regexLib.xmlComments, css: 'comments' }, // + { regex: new XRegExp('(<|<)[\\s\\/\\?]*(\\w+)(?.*?)[\\s\\/\\?]*(>|>)', 'sg'), func: process } + ]; + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['xml', 'xhtml', 'xslt', 'html']; + + SyntaxHighlighter.brushes.Xml = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shCore.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shCore.js new file mode 100755 index 0000000..b47b645 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shCore.js @@ -0,0 +1,17 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('K M;I(M)1S 2U("2a\'t 4k M 4K 2g 3l 4G 4H");(6(){6 r(f,e){I(!M.1R(f))1S 3m("3s 15 4R");K a=f.1w;f=M(f.1m,t(f)+(e||""));I(a)f.1w={1m:a.1m,19:a.19?a.19.1a(0):N};H f}6 t(f){H(f.1J?"g":"")+(f.4s?"i":"")+(f.4p?"m":"")+(f.4v?"x":"")+(f.3n?"y":"")}6 B(f,e,a,b){K c=u.L,d,h,g;v=R;5K{O(;c--;){g=u[c];I(a&g.3r&&(!g.2p||g.2p.W(b))){g.2q.12=e;I((h=g.2q.X(f))&&h.P===e){d={3k:g.2b.W(b,h,a),1C:h};1N}}}}5v(i){1S i}5q{v=11}H d}6 p(f,e,a){I(3b.Z.1i)H f.1i(e,a);O(a=a||0;a-1},3d:6(g){e+=g}};c1&&p(e,"")>-1){a=15(J.1m,n.Q.W(t(J),"g",""));n.Q.W(f.1a(e.P),a,6(){O(K c=1;c<14.L-2;c++)I(14[c]===1d)e[c]=1d})}I(J.1w&&J.1w.19)O(K b=1;be.P&&J.12--}H e};I(!D)15.Z.1A=6(f){(f=n.X.W(J,f))&&J.1J&&!f[0].L&&J.12>f.P&&J.12--;H!!f};1r.Z.1C=6(f){M.1R(f)||(f=15(f));I(f.1J){K e=n.1C.1p(J,14);f.12=0;H e}H f.X(J)};1r.Z.Q=6(f,e){K a=M.1R(f),b,c;I(a&&1j e.58()==="3f"&&e.1i("${")===-1&&y)H n.Q.1p(J,14);I(a){I(f.1w)b=f.1w.19}Y f+="";I(1j e==="6")c=n.Q.W(J,f,6(){I(b){14[0]=1f 1r(14[0]);O(K d=0;dd.L-3;){i=1r.Z.1a.W(g,-1)+i;g=1Q.3i(g/10)}H(g?d[g]||"":"$")+i}Y{g=+i;I(g<=d.L-3)H d[g];g=b?p(b,i):-1;H g>-1?d[g+1]:h}})})}I(a&&f.1J)f.12=0;H c};1r.Z.1e=6(f,e){I(!M.1R(f))H n.1e.1p(J,14);K a=J+"",b=[],c=0,d,h;I(e===1d||+e<0)e=5D;Y{e=1Q.3i(+e);I(!e)H[]}O(f=M.3c(f);d=f.X(a);){I(f.12>c){b.U(a.1a(c,d.P));d.L>1&&d.P=e)1N}f.12===d.P&&f.12++}I(c===a.L){I(!n.1A.W(f,"")||h)b.U("")}Y b.U(a.1a(c));H b.L>e?b.1a(0,e):b};M.1h(/\\(\\?#[^)]*\\)/,6(f){H n.1A.W(A,f.2S.1a(f.P+f[0].L))?"":"(?:)"});M.1h(/\\((?!\\?)/,6(){J.19.U(N);H"("});M.1h(/\\(\\?<([$\\w]+)>/,6(f){J.19.U(f[1]);J.2N=R;H"("});M.1h(/\\\\k<([\\w$]+)>/,6(f){K e=p(J.19,f[1]);H e>-1?"\\\\"+(e+1)+(3R(f.2S.3a(f.P+f[0].L))?"":"(?:)"):f[0]});M.1h(/\\[\\^?]/,6(f){H f[0]==="[]"?"\\\\b\\\\B":"[\\\\s\\\\S]"});M.1h(/^\\(\\?([5A]+)\\)/,6(f){J.3d(f[1]);H""});M.1h(/(?:\\s+|#.*)+/,6(f){H n.1A.W(A,f.2S.1a(f.P+f[0].L))?"":"(?:)"},M.1B,6(){H J.2K("x")});M.1h(/\\./,6(){H"[\\\\s\\\\S]"},M.1B,6(){H J.2K("s")})})();1j 2e!="1d"&&(2e.M=M);K 1v=6(){6 r(a,b){a.1l.1i(b)!=-1||(a.1l+=" "+b)}6 t(a){H a.1i("3e")==0?a:"3e"+a}6 B(a){H e.1Y.2A[t(a)]}6 p(a,b,c){I(a==N)H N;K d=c!=R?a.3G:[a.2G],h={"#":"1c",".":"1l"}[b.1o(0,1)]||"3h",g,i;g=h!="3h"?b.1o(1):b.5u();I((a[h]||"").1i(g)!=-1)H a;O(a=0;d&&a\'+c+""});H a}6 n(a,b){a.1e("\\n");O(K c="",d=0;d<50;d++)c+=" ";H a=v(a,6(h){I(h.1i("\\t")==-1)H h;O(K g=0;(g=h.1i("\\t"))!=-1;)h=h.1o(0,g)+c.1o(0,b-g%b)+h.1o(g+1,h.L);H h})}6 x(a){H a.Q(/^\\s+|\\s+$/g,"")}6 D(a,b){I(a.Pb.P)H 1;Y I(a.Lb.L)H 1;H 0}6 y(a,b){6 c(k){H k[0]}O(K d=N,h=[],g=b.2D?b.2D:c;(d=b.1I.X(a))!=N;){K i=g(d,b);I(1j i=="3f")i=[1f e.2L(i,d.P,b.23)];h=h.1O(i)}H h}6 E(a){K b=/(.*)((&1G;|&1y;).*)/;H a.Q(e.3A.3M,6(c){K d="",h=N;I(h=b.X(c)){c=h[1];d=h[2]}H\'\'+c+""+d})}6 z(){O(K a=1E.36("1k"),b=[],c=0;c<1z 4I="1Z://2y.3L.3K/4L/5L"><3J><4N 1Z-4M="5G-5M" 6K="2O/1z; 6J=6I-8" /><1t>6L 1v<3B 1L="25-6M:6Q,6P,6O,6N-6F;6y-2f:#6x;2f:#6w;25-22:6v;2O-3D:3C;">1v3v 3.0.76 (72 73 3x)1Z://3u.2w/1v70 17 6U 71.6T 6X-3x 6Y 6D.6t 61 60 J 1k, 5Z 5R 5V <2R/>5U 5T 5S!\'}},1Y:{2j:N,2A:{}},1U:{},3A:{6n:/\\/\\*[\\s\\S]*?\\*\\//2c,6m:/\\/\\/.*$/2c,6l:/#.*$/2c,6k:/"([^\\\\"\\n]|\\\\.)*"/g,6o:/\'([^\\\\\'\\n]|\\\\.)*\'/g,6p:1f M(\'"([^\\\\\\\\"]|\\\\\\\\.)*"\',"3z"),6s:1f M("\'([^\\\\\\\\\']|\\\\\\\\.)*\'","3z"),6q:/(&1y;|<)!--[\\s\\S]*?--(&1G;|>)/2c,3M:/\\w+:\\/\\/[\\w-.\\/?%&=:@;]*/g,6a:{18:/(&1y;|<)\\?=?/g,1b:/\\?(&1G;|>)/g},69:{18:/(&1y;|<)%=?/g,1b:/%(&1G;|>)/g},6d:{18:/(&1y;|<)\\s*1k.*?(&1G;|>)/2T,1b:/(&1y;|<)\\/\\s*1k\\s*(&1G;|>)/2T}},16:{1H:6(a){6 b(i,k){H e.16.2o(i,k,e.13.1x[k])}O(K c=\'\',d=e.16.2x,h=d.2X,g=0;g";H c},2o:6(a,b,c){H\'<2W>\'+c+""},2b:6(a){K b=a.1F,c=b.1l||"";b=B(p(b,".20",R).1c);K d=6(h){H(h=15(h+"6f(\\\\w+)").X(c))?h[1]:N}("6g");b&&d&&e.16.2x[d].2B(b);a.3N()},2x:{2X:["21","2P"],21:{1H:6(a){I(a.V("2l")!=R)H"";K b=a.V("1t");H e.16.2o(a,"21",b?b:e.13.1x.21)},2B:6(a){a=1E.6j(t(a.1c));a.1l=a.1l.Q("47","")}},2P:{2B:6(){K a="68=0";a+=", 18="+(31.30-33)/2+", 32="+(31.2Z-2Y)/2+", 30=33, 2Z=2Y";a=a.Q(/^,/,"");a=1P.6Z("","38",a);a.2C();K b=a.1E;b.6W(e.13.1x.37);b.6V();a.2C()}}}},35:6(a,b){K c;I(b)c=[b];Y{c=1E.36(e.13.34);O(K d=[],h=0;h(.*?))\\\\]$"),s=1f M("(?<27>[\\\\w-]+)\\\\s*:\\\\s*(?<1T>[\\\\w-%#]+|\\\\[.*?\\\\]|\\".*?\\"|\'.*?\')\\\\s*;?","g");(j=s.X(k))!=N;){K o=j.1T.Q(/^[\'"]|[\'"]$/g,"");I(o!=N&&m.1A(o)){o=m.X(o);o=o.2V.L>0?o.2V.1e(/\\s*,\\s*/):[]}l[j.27]=o}g={1F:g,1n:C(i,l)};g.1n.1D!=N&&d.U(g)}H d},1M:6(a,b){K c=J.35(a,b),d=N,h=e.13;I(c.L!==0)O(K g=0;g")==o-3){m=m.4h(0,o-3);s=R}l=s?m:l}I((i.1t||"")!="")k.1t=i.1t;k.1D=j;d.2Q(k);b=d.2F(l);I((i.1c||"")!="")b.1c=i.1c;i.2G.74(b,i)}}},2E:6(a){w(1P,"4k",6(){e.1M(a)})}};e.2E=e.2E;e.1M=e.1M;e.2L=6(a,b,c){J.1T=a;J.P=b;J.L=a.L;J.23=c;J.1V=N};e.2L.Z.1q=6(){H J.1T};e.4l=6(a){6 b(j,l){O(K m=0;md)1N;Y I(g.P==c.P&&g.L>c.L)a[b]=N;Y I(g.P>=c.P&&g.P\'+c+""},3Q:6(a,b){K c="",d=a.1e("\\n").L,h=2u(J.V("2i-1s")),g=J.V("2z-1s-2t");I(g==R)g=(h+d-1).1q().L;Y I(3R(g)==R)g=0;O(K i=0;i\'+j+"":"")+i)}H a},4f:6(a){H a?"<4a>"+a+"":""},4b:6(a,b){6 c(l){H(l=l?l.1V||g:g)?l+" ":""}O(K d=0,h="",g=J.V("1D",""),i=0;i|&1y;2R\\s*\\/?&1G;/2T;I(e.13.46==R)b=b.Q(h,"\\n");I(e.13.44==R)b=b.Q(h,"");b=b.1e("\\n");h=/^\\s*/;g=4Q;O(K i=0;i0;i++){K k=b[i];I(x(k).L!=0){k=h.X(k);I(k==N){a=a;1N a}g=1Q.4q(k[0].L,g)}}I(g>0)O(i=0;i\'+(J.V("16")?e.16.1H(J):"")+\'<3Z 5z="0" 5H="0" 5J="0">\'+J.4f(J.V("1t"))+"<3T><3P>"+(1u?\'<2d 1g="1u">\'+J.3Q(a)+"":"")+\'<2d 1g="17">\'+b+""},2F:6(a){I(a===N)a="";J.17=a;K b=J.3Y("T");b.3X=J.1H(a);J.V("16")&&w(p(b,".16"),"5c",e.16.2b);J.V("3V-17")&&w(p(b,".17"),"56",f);H b},2Q:6(a){J.1c=""+1Q.5d(1Q.5n()*5k).1q();e.1Y.2A[t(J.1c)]=J;J.1n=C(e.2v,a||{});I(J.V("2k")==R)J.1n.16=J.1n.1u=11},5j:6(a){a=a.Q(/^\\s+|\\s+$/g,"").Q(/\\s+/g,"|");H"\\\\b(?:"+a+")\\\\b"},5f:6(a){J.28={18:{1I:a.18,23:"1k"},1b:{1I:a.1b,23:"1k"},17:1f M("(?<18>"+a.18.1m+")(?<17>.*?)(?<1b>"+a.1b.1m+")","5o")}}};H e}();1j 2e!="1d"&&(2e.1v=1v);',62,441,'||||||function|||||||||||||||||||||||||||||||||||||return|if|this|var|length|XRegExp|null|for|index|replace|true||div|push|getParam|call|exec|else|prototype||false|lastIndex|config|arguments|RegExp|toolbar|code|left|captureNames|slice|right|id|undefined|split|new|class|addToken|indexOf|typeof|script|className|source|params|substr|apply|toString|String|line|title|gutter|SyntaxHighlighter|_xregexp|strings|lt|html|test|OUTSIDE_CLASS|match|brush|document|target|gt|getHtml|regex|global|join|style|highlight|break|concat|window|Math|isRegExp|throw|value|brushes|brushName|space|alert|vars|http|syntaxhighlighter|expandSource|size|css|case|font|Fa|name|htmlScript|dA|can|handler|gm|td|exports|color|in|href|first|discoveredBrushes|light|collapse|object|cache|getButtonHtml|trigger|pattern|getLineHtml|nbsp|numbers|parseInt|defaults|com|items|www|pad|highlighters|execute|focus|func|all|getDiv|parentNode|navigator|INSIDE_CLASS|regexList|hasFlag|Match|useScriptTags|hasNamedCapture|text|help|init|br|input|gi|Error|values|span|list|250|height|width|screen|top|500|tagName|findElements|getElementsByTagName|aboutDialog|_blank|appendChild|charAt|Array|copyAsGlobal|setFlag|highlighter_|string|attachEvent|nodeName|floor|backref|output|the|TypeError|sticky|Za|iterate|freezeTokens|scope|type|textarea|alexgorbatchev|version|margin|2010|005896|gs|regexLib|body|center|align|noBrush|require|childNodes|DTD|xhtml1|head|org|w3|url|preventDefault|container|tr|getLineNumbersHtml|isNaN|userAgent|tbody|isLineHighlighted|quick|void|innerHTML|create|table|links|auto|smart|tab|stripBrs|tabs|bloggerMode|collapsed|plain|getCodeLinesHtml|caption|getMatchesHtml|findMatches|figureOutLineNumbers|removeNestedMatches|getTitleHtml|brushNotHtmlScript|substring|createElement|Highlighter|load|HtmlScript|Brush|pre|expand|multiline|min|Can|ignoreCase|find|blur|extended|toLowerCase|aliases|addEventListener|innerText|textContent|wasn|select|createTextNode|removeChild|option|same|frame|xmlns|dtd|twice|1999|equiv|meta|htmlscript|transitional|1E3|expected|PUBLIC|DOCTYPE|on|W3C|XHTML|TR|EN|Transitional||configured|srcElement|Object|after|run|dblclick|matchChain|valueOf|constructor|default|switch|click|round|execAt|forHtmlScript|token|gimy|functions|getKeywords|1E6|escape|within|random|sgi|another|finally|supply|MSIE|ie|toUpperCase|catch|returnValue|definition|event|border|imsx|constructing|one|Infinity|from|when|Content|cellpadding|flags|cellspacing|try|xhtml|Type|spaces|2930402|hosted_button_id|lastIndexOf|donate|active|development|keep|to|xclick|_s|Xml|please|like|you|paypal|cgi|cmd|webscr|bin|highlighted|scrollbars|aspScriptTags|phpScriptTags|sort|max|scriptScriptTags|toolbar_item|_|command|command_|number|getElementById|doubleQuotedString|singleLinePerlComments|singleLineCComments|multiLineCComments|singleQuotedString|multiLineDoubleQuotedString|xmlComments|alt|multiLineSingleQuotedString|If|https|1em|000|fff|background|5em|xx|bottom|75em|Gorbatchev|large|serif|CDATA|continue|utf|charset|content|About|family|sans|Helvetica|Arial|Geneva|3em|nogutter|Copyright|syntax|close|write|2004|Alex|open|JavaScript|highlighter|July|02|replaceChild|offset|83'.split('|'),0,{})) diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shLegacy.js b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shLegacy.js new file mode 100755 index 0000000..6d9fd4d --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/scripts/shLegacy.js @@ -0,0 +1,17 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('3 u={8:{}};u.8={A:4(c,k,l,m,n,o){4 d(a,b){2 a!=1?a:b}4 f(a){2 a!=1?a.E():1}c=c.I(":");3 g=c[0],e={};t={"r":K};M=1;5=8.5;9(3 j R c)e[c[j]]="r";k=f(d(k,5.C));l=f(d(l,5.D));m=f(d(m,5.s));o=f(d(o,5.Q));n=f(d(n,5["x-y"]));2{P:g,C:d(t[e.O],k),D:d(t[e.N],l),s:d({"r":r}[e.s],m),"x-y":d(4(a,b){9(3 h=T S("^"+b+"\\\\[(?\\\\w+)\\\\]$","U"),i=1,p=0;p tags to the document body + for (i = 0; i < elements.length; i++) + { + var url = brushes[elements[i].params.brush]; + + if (!url) + continue; + + scripts[url] = false; + loadScript(url); + } + + function loadScript(url) + { + var script = document.createElement('script'), + done = false + ; + + script.src = url; + script.type = 'text/javascript'; + script.language = 'javascript'; + script.onload = script.onreadystatechange = function() + { + if (!done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) + { + done = true; + scripts[url] = true; + checkAll(); + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + script.parentNode.removeChild(script); + } + }; + + // sync way of adding script tags to the page + document.body.appendChild(script); + }; + + function checkAll() + { + for(var url in scripts) + if (scripts[url] == false) + return; + + if (allCalled) + SyntaxHighlighter.highlight(allParams); + }; +}; + +})(); diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/src/shCore.js b/clipper/Documentation/Scripts/SyntaxHighlighter/src/shCore.js new file mode 100755 index 0000000..4214763 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/src/shCore.js @@ -0,0 +1,1721 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +// +// Begin anonymous function. This is used to contain local scope variables without polutting global scope. +// +var SyntaxHighlighter = function() { + +// CommonJS +if (typeof(require) != 'undefined' && typeof(XRegExp) == 'undefined') +{ + XRegExp = require('XRegExp').XRegExp; +} + +// Shortcut object which will be assigned to the SyntaxHighlighter variable. +// This is a shorthand for local reference in order to avoid long namespace +// references to SyntaxHighlighter.whatever... +var sh = { + defaults : { + /** Additional CSS class names to be added to highlighter elements. */ + 'class-name' : '', + + /** First line number. */ + 'first-line' : 1, + + /** + * Pads line numbers. Possible values are: + * + * false - don't pad line numbers. + * true - automaticaly pad numbers with minimum required number of leading zeroes. + * [int] - length up to which pad line numbers. + */ + 'pad-line-numbers' : false, + + /** Lines to highlight. */ + 'highlight' : null, + + /** Title to be displayed above the code block. */ + 'title' : null, + + /** Enables or disables smart tabs. */ + 'smart-tabs' : true, + + /** Gets or sets tab size. */ + 'tab-size' : 4, + + /** Enables or disables gutter. */ + 'gutter' : true, + + /** Enables or disables toolbar. */ + 'toolbar' : true, + + /** Enables quick code copy and paste from double click. */ + 'quick-code' : true, + + /** Forces code view to be collapsed. */ + 'collapse' : false, + + /** Enables or disables automatic links. */ + 'auto-links' : true, + + /** Gets or sets light mode. Equavalent to turning off gutter and toolbar. */ + 'light' : false, + + 'html-script' : false + }, + + config : { + space : ' ', + + /** Enables use of tags. */ + scriptScriptTags : { left: /(<|<)\s*script.*?(>|>)/gi, right: /(<|<)\/\s*script\s*(>|>)/gi } + }, + + toolbar: { + /** + * Generates HTML markup for the toolbar. + * @param {Highlighter} highlighter Highlighter instance. + * @return {String} Returns HTML markup. + */ + getHtml: function(highlighter) + { + var html = '
', + items = sh.toolbar.items, + list = items.list + ; + + function defaultGetHtml(highlighter, name) + { + return sh.toolbar.getButtonHtml(highlighter, name, sh.config.strings[name]); + }; + + for (var i = 0; i < list.length; i++) + html += (items[list[i]].getHtml || defaultGetHtml)(highlighter, list[i]); + + html += '
'; + + return html; + }, + + /** + * Generates HTML markup for a regular button in the toolbar. + * @param {Highlighter} highlighter Highlighter instance. + * @param {String} commandName Command name that would be executed. + * @param {String} label Label text to display. + * @return {String} Returns HTML markup. + */ + getButtonHtml: function(highlighter, commandName, label) + { + return '' + label + '' + ; + }, + + /** + * Event handler for a toolbar anchor. + */ + handler: function(e) + { + var target = e.target, + className = target.className || '' + ; + + function getValue(name) + { + var r = new RegExp(name + '_(\\w+)'), + match = r.exec(className) + ; + + return match ? match[1] : null; + }; + + var highlighter = getHighlighterById(findParentElement(target, '.syntaxhighlighter').id), + commandName = getValue('command') + ; + + // execute the toolbar command + if (highlighter && commandName) + sh.toolbar.items[commandName].execute(highlighter); + + // disable default A click behaviour + e.preventDefault(); + }, + + /** Collection of toolbar items. */ + items : { + // Ordered lis of items in the toolbar. Can't expect `for (var n in items)` to be consistent. + list: ['expandSource', 'help'], + + expandSource: { + getHtml: function(highlighter) + { + if (highlighter.getParam('collapse') != true) + return ''; + + var title = highlighter.getParam('title'); + return sh.toolbar.getButtonHtml(highlighter, 'expandSource', title ? title : sh.config.strings.expandSource); + }, + + execute: function(highlighter) + { + var div = getHighlighterDivById(highlighter.id); + removeClass(div, 'collapsed'); + } + }, + + /** Command to display the about dialog window. */ + help: { + execute: function(highlighter) + { + var wnd = popup('', '_blank', 500, 250, 'scrollbars=0'), + doc = wnd.document + ; + + doc.write(sh.config.strings.aboutDialog); + doc.close(); + wnd.focus(); + } + } + } + }, + + /** + * Finds all elements on the page which should be processes by SyntaxHighlighter. + * + * @param {Object} globalParams Optional parameters which override element's + * parameters. Only used if element is specified. + * + * @param {Object} element Optional element to highlight. If none is + * provided, all elements in the current document + * are returned which qualify. + * + * @return {Array} Returns list of { target: DOMElement, params: Object } objects. + */ + findElements: function(globalParams, element) + { + var elements = element ? [element] : toArray(document.getElementsByTagName(sh.config.tagName)), + conf = sh.config, + result = [] + ; + + // support for \ No newline at end of file diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/brushes_tests.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/brushes_tests.html new file mode 100755 index 0000000..e1e2068 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/brushes_tests.html @@ -0,0 +1,136 @@ + + + + + SyntaxHighlighter Brushes Tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/001_basic.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/001_basic.html new file mode 100755 index 0000000..1d57334 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/001_basic.html @@ -0,0 +1,42 @@ +
+	/**
+	 * multiline comment 
+	 */
+	
+	text
+	
+	// single line comment
+	
+	text
+	
+	"string" text 'string' text "string"
+	"string with \" escape" text 'string with \' escape' text "string with \" escape"
+	
+	var code = '\
+		function helloWorld()\
+		{\
+			// this is great!\
+			for(var i = 0; i <= 1; i++)\
+				alert("yay");\
+		}\
+		';
+
+ + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/002_brushes.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/002_brushes.html new file mode 100755 index 0000000..e9c64e2 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/002_brushes.html @@ -0,0 +1,50 @@ +
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
test
+
+ + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/003_script_tag.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/003_script_tag.html new file mode 100755 index 0000000..9b44292 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/003_script_tag.html @@ -0,0 +1,42 @@ +
+ +
+ + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/004_url_parsing.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/004_url_parsing.html new file mode 100755 index 0000000..d489a66 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/004_url_parsing.html @@ -0,0 +1,43 @@ +
+/**
+ * Please see <http://www.alexgorbatchev.come/?test=1&y=2>
+ */
+var home = "http://www.alexgorbatchev.come/?test=1&y=2;test/1/2/3;";
+// < http://www.gnu.org/licenses/?test=1&y=2 >.
+
+// Test embedded URLs that terminate at a left angle bracket.
+// See bug #28: http://bitbucket.org/alexg/syntaxhighlighter/issue/28/
+"http://www.example.com/song2.mp3";
+
+ + \ No newline at end of file diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/005_no_gutter.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/005_no_gutter.html new file mode 100755 index 0000000..6e6b1be --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/005_no_gutter.html @@ -0,0 +1,33 @@ +
+			public Image getImage(URL url, String name) {
+				try {
+					/*
+					   Regular multiline comment.
+					*/
+				    return getImage(new URL(url, name));
+				} catch (MalformedURLException e) {
+				    return null;
+				}
+			}
+
+ + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/006_pad_line_numbers.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/006_pad_line_numbers.html new file mode 100755 index 0000000..8ebdd55 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/006_pad_line_numbers.html @@ -0,0 +1,39 @@ +
+/**
+ * Returns an Image object that can then be painted on the screen. 
+ * The url argument must specify an absolute {@link URL}. The name
+ * argument is a specifier that is relative to the url argument. 
+ *
+ * @param  url  an absolute URL giving the base location of the image
+ * @param  name the location of the image, relative to the url argument
+ * @return      the image at the specified URL
+ * @see         Image
+ */
+
+ + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/007_collapse.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/007_collapse.html new file mode 100755 index 0000000..2643bb5 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/007_collapse.html @@ -0,0 +1,60 @@ +
+	/**
+	 * Returns an Image object that can then be painted on the screen. 
+	 * The url argument must specify an absolute {@link URL}. The name
+	 * argument is a specifier that is relative to the url argument. 
+	 *
+	 * @param  url  an absolute URL giving the base location of the image
+	 * @param  name the location of the image, relative to the url argument
+	 * @return      the image at the specified URL
+	 * @see         Image
+	 */
+
+ +
+	/**
+	 * Returns an Image object that can then be painted on the screen. 
+	 * The url argument must specify an absolute {@link URL}. The name
+	 * argument is a specifier that is relative to the url argument. 
+	 *
+	 * @param  url  an absolute URL giving the base location of the image
+	 * @param  name the location of the image, relative to the url argument
+	 * @return      the image at the specified URL
+	 * @see         Image
+	 */
+
+ + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/007_collapse_interaction.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/007_collapse_interaction.html new file mode 100755 index 0000000..ea72046 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/007_collapse_interaction.html @@ -0,0 +1,44 @@ + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/008_first_line.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/008_first_line.html new file mode 100755 index 0000000..169dc38 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/008_first_line.html @@ -0,0 +1,29 @@ +
+	partial class Foo
+	{
+		function test()
+		{
+			yield return;
+		}
+	}
+
+ + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/009_class_name.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/009_class_name.html new file mode 100755 index 0000000..f2437d5 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/009_class_name.html @@ -0,0 +1,32 @@ +
+	public Image getImage(URL url, String name) {
+		try {
+			/*
+			   Regular multiline comment.
+			*/
+		    return getImage(new URL(url, name));
+		} catch (MalformedURLException e) {
+		    return null;
+		}
+	}
+
+ + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/010_highlight.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/010_highlight.html new file mode 100755 index 0000000..64b6613 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/010_highlight.html @@ -0,0 +1,70 @@ +
+	public function validateStrongPassword(password:String):Boolean
+	{
+		if (password == null || password.length <= 0)
+		{
+			return false;
+		}
+		
+		return STRONG_PASSWORD_PATTERN.test(password);
+	}
+
+ + + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/011_smart_tabs.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/011_smart_tabs.html new file mode 100755 index 0000000..d6d62fc --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/011_smart_tabs.html @@ -0,0 +1,98 @@ +
+	the		words	in		this	paragraph
+	should	look	like	they	are
+	evenly	spaced	between	columns
+
+ +
+	the	words	in	this	paragraph
+	should	look	like	they	are
+	evenly	spaced	between	columns
+
+ +
+	the		words	in		this	paragraph
+	should	look	out		of		whack
+	because	smart	tabs	are		disabled
+
+ + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/012_server_side.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/012_server_side.html new file mode 100755 index 0000000..1bb4217 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/012_server_side.html @@ -0,0 +1,35 @@ + + +
+
+ + \ No newline at end of file diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/013_html_script.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/013_html_script.html new file mode 100755 index 0000000..c0a1201 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/013_html_script.html @@ -0,0 +1,34 @@ +
+<hello>
+	<%
+		package free.cafekiwi.gotapi;
+	%>
+</hello>
+
+
+<%= print(); %>
+
+ + \ No newline at end of file diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/014_legacy.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/014_legacy.html new file mode 100755 index 0000000..eb4343e --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/cases/014_legacy.html @@ -0,0 +1,70 @@ +
basic check
+
no toolbar
+
no gutter
+
collapsed
+
first line
+ + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/commonjs_tests.js b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/commonjs_tests.js new file mode 100755 index 0000000..cda8162 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/commonjs_tests.js @@ -0,0 +1,52 @@ +/** + * This is a CommonJS compatibility test. You can run this file with node. + */ +require.paths.unshift(__dirname + '/../scripts'); + +var sys = require('sys'), + shSyntaxHighlighter = require('shCore').SyntaxHighlighter, + code = 'test', + brushes = [ + 'AS3', + 'AppleScript', + 'Bash', + 'CSharp', + 'ColdFusion', + 'Cpp', + 'Css', + 'Delphi', + 'Diff', + 'Erlang', + 'Groovy', + 'JScript', + 'Java', + 'JavaFX', + 'Perl', + 'Php', + 'Plain', + 'PowerShell', + 'Python', + 'Ruby', + 'Sass', + 'Scala', + 'Sql', + 'Vb', + 'Xml' + ] + ; + +brushes.sort(); + +for (var i = 0; i < brushes.length; i++) +{ + var name = brushes[i], + brush = require('shBrush' + name).Brush + ; + + brush = new brush(); + brush.init({ toolbar: false }); + + var result = brush.getHtml(code); + + sys.puts(name + (result != null ? ': ok' : ': NOT OK')); +} diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/js/jquery-1.4.2.js b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/js/jquery-1.4.2.js new file mode 100755 index 0000000..e414a7e --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/js/jquery-1.4.2.js @@ -0,0 +1,6240 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function( window, undefined ) { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/, + + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + rtrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // Has the ready events already been bound? + readyBound = false, + + // The functions to execute on DOM ready + readyList = [], + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwnProperty = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + indexOf = Array.prototype.indexOf; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context ) { + this.context = document; + this[0] = document.body; + this.selector = "body"; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + if ( elem ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $("TAG") + } else if ( !context && /^\w+$/.test( selector ) ) { + this.selector = selector; + this.context = document; + selector = document.getElementsByTagName( selector ); + return jQuery.merge( this, selector ); + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return jQuery( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.4.2", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // If the DOM is already ready + if ( jQuery.isReady ) { + // Execute the function immediately + fn.call( document, jQuery ); + + // Otherwise, remember the function for later + } else if ( readyList ) { + // Add the function to the wait list + readyList.push( fn ); + } + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || jQuery(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging object literal values or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) { + var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src + : jQuery.isArray(copy) ? [] : {}; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // Handle when the DOM is ready + ready: function() { + // Make sure that the DOM is not already loaded + if ( !jQuery.isReady ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 13 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If there are functions bound, to execute + if ( readyList ) { + // Execute all of them + var fn, i = 0; + while ( (fn = readyList[ i++ ]) ) { + fn.call( document, jQuery ); + } + + // Reset the list of functions + readyList = null; + } + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyBound ) { + return; + } + + readyBound = true; + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + return jQuery.ready(); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return toString.call(obj) === "[object Function]"; + }, + + isArray: function( obj ) { + return toString.call(obj) === "[object Array]"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) { + return false; + } + + // Not own constructor property must be Object + if ( obj.constructor + && !hasOwnProperty.call(obj, "constructor") + && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwnProperty.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@") + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]") + .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) { + + // Try to use the native JSON parser first + return window.JSON && window.JSON.parse ? + window.JSON.parse( data ) : + (new Function("return " + data))(); + + } else { + jQuery.error( "Invalid JSON: " + data ); + } + }, + + noop: function() {}, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && rnotwhite.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + + if ( jQuery.support.scriptEval ) { + script.appendChild( document.createTextNode( data ) ); + } else { + script.text = data; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} + } + } + + return object; + }, + + trim: function( text ) { + return (text || "").replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || (typeof array !== "function" && array.setInterval) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = []; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + if ( !inv !== !callback( elems[ i ], i ) ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var ret = [], value; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + proxy: function( fn, proxy, thisObject ) { + if ( arguments.length === 2 ) { + if ( typeof proxy === "string" ) { + thisObject = fn; + fn = thisObject[ proxy ]; + proxy = undefined; + + } else if ( proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + } + + if ( !proxy && fn ) { + proxy = function() { + return fn.apply( thisObject || this, arguments ); + }; + } + + // Set the guid of unique handler to the same of original handler, so it can be removed + if ( fn ) { + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + } + + // So proxy can be declared as an argument + return proxy; + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = /(webkit)[ \/]([\w.]+)/.exec( ua ) || + /(opera)(?:.*version)?[ \/]([\w.]+)/.exec( ua ) || + /(msie) ([\w.]+)/.exec( ua ) || + !/compatible/.test( ua ) && /(mozilla)(?:.*? rv:([\w.]+))?/.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + browser: {} +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +if ( indexOf ) { + jQuery.inArray = function( elem, array ) { + return indexOf.call( array, elem ); + }; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch( error ) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +function evalScript( i, elem ) { + if ( elem.src ) { + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + } else { + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } +} + +// Mutifunctional method to get and set values to a collection +// The value/s can be optionally by executed if its a function +function access( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; +} + +function now() { + return (new Date).getTime(); +} +(function() { + + jQuery.support = {}; + + var root = document.documentElement, + script = document.createElement("script"), + div = document.createElement("div"), + id = "script" + now(); + + div.style.display = "none"; + div.innerHTML = "
a"; + + var all = div.getElementsByTagName("*"), + a = div.getElementsByTagName("a")[0]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return; + } + + jQuery.support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText insted) + style: /red/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: div.getElementsByTagName("input")[0].value === "on", + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: document.createElement("select").appendChild( document.createElement("option") ).selected, + + parentNode: div.removeChild( div.appendChild( document.createElement("div") ) ).parentNode === null, + + // Will be defined later + deleteExpando: true, + checkClone: false, + scriptEval: false, + noCloneEvent: true, + boxModel: null + }; + + script.type = "text/javascript"; + try { + script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); + } catch(e) {} + + root.insertBefore( script, root.firstChild ); + + // Make sure that the execution of code works by injecting a script + // tag with appendChild/createTextNode + // (IE doesn't support this, fails, and uses .text instead) + if ( window[ id ] ) { + jQuery.support.scriptEval = true; + delete window[ id ]; + } + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete script.test; + + } catch(e) { + jQuery.support.deleteExpando = false; + } + + root.removeChild( script ); + + if ( div.attachEvent && div.fireEvent ) { + div.attachEvent("onclick", function click() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + jQuery.support.noCloneEvent = false; + div.detachEvent("onclick", click); + }); + div.cloneNode(true).fireEvent("onclick"); + } + + div = document.createElement("div"); + div.innerHTML = ""; + + var fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + + // Figure out if the W3C box model works as expected + // document.body must exist before we can do this + jQuery(function() { + var div = document.createElement("div"); + div.style.width = div.style.paddingLeft = "1px"; + + document.body.appendChild( div ); + jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; + document.body.removeChild( div ).style.display = 'none'; + + div = null; + }); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + var eventSupported = function( eventName ) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + var isSupported = (eventName in el); + if ( !isSupported ) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; + + return isSupported; + }; + + jQuery.support.submitBubbles = eventSupported("submit"); + jQuery.support.changeBubbles = eventSupported("change"); + + // release memory in IE + root = script = div = all = a = null; +})(); + +jQuery.props = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" +}; +var expando = "jQuery" + now(), uuid = 0, windowData = {}; + +jQuery.extend({ + cache: {}, + + expando:expando, + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + "object": true, + "applet": true + }, + + data: function( elem, name, data ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ], cache = jQuery.cache, thisCache; + + if ( !id && typeof name === "string" && data === undefined ) { + return null; + } + + // Compute a unique ID for the element + if ( !id ) { + id = ++uuid; + } + + // Avoid generating a new cache unless none exists and we + // want to manipulate it. + if ( typeof name === "object" ) { + elem[ expando ] = id; + thisCache = cache[ id ] = jQuery.extend(true, {}, name); + + } else if ( !cache[ id ] ) { + elem[ expando ] = id; + cache[ id ] = {}; + } + + thisCache = cache[ id ]; + + // Prevent overriding the named cache with undefined values + if ( data !== undefined ) { + thisCache[ name ] = data; + } + + return typeof name === "string" ? thisCache[ name ] : thisCache; + }, + + removeData: function( elem, name ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ], cache = jQuery.cache, thisCache = cache[ id ]; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( thisCache ) { + // Remove the section of cache data + delete thisCache[ name ]; + + // If we've removed all the data, remove the element's cache + if ( jQuery.isEmptyObject(thisCache) ) { + jQuery.removeData( elem ); + } + } + + // Otherwise, we want to remove all of the element's data + } else { + if ( jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } + + // Completely remove the data cache + delete cache[ id ]; + } + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + if ( typeof key === "undefined" && this.length ) { + return jQuery.data( this[0] ); + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + } + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } else { + return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function() { + jQuery.data( this, key, value ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); +jQuery.extend({ + queue: function( elem, type, data ) { + if ( !elem ) { + return; + } + + type = (type || "fx") + "queue"; + var q = jQuery.data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( !data ) { + return q || []; + } + + if ( !q || jQuery.isArray(data) ) { + q = jQuery.data( elem, type, jQuery.makeArray(data) ); + + } else { + q.push( data ); + } + + return q; + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), fn = queue.shift(); + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function( i, elem ) { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + } +}); +var rclass = /[\n\t]/g, + rspace = /\s+/, + rreturn = /\r/g, + rspecialurl = /href|src|style/, + rtype = /(button|input)/i, + rfocusable = /(button|input|object|select|textarea)/i, + rclickable = /^(a|area)$/i, + rradiocheck = /radio|checkbox/; + +jQuery.fn.extend({ + attr: function( name, value ) { + return access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name, fn ) { + return this.each(function(){ + jQuery.attr( this, name, "" ); + if ( this.nodeType === 1 ) { + this.removeAttribute( name ); + } + }); + }, + + addClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.addClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( value && typeof value === "string" ) { + var classNames = (value || "").split( rspace ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className ) { + elem.className = value; + + } else { + var className = " " + elem.className + " ", setClass = elem.className; + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { + setClass += " " + classNames[c]; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.removeClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + var classNames = (value || "").split(rspace); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + var className = (" " + elem.className + " ").replace(rclass, " "); + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[c] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this); + self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, i = 0, self = jQuery(this), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery.data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + if ( value === undefined ) { + var elem = this[0]; + + if ( elem ) { + if ( jQuery.nodeName( elem, "option" ) ) { + return (elem.attributes.value || {}).specified ? elem.value : elem.text; + } + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { + return elem.getAttribute("value") === null ? "on" : elem.value; + } + + + // Everything else, we just grab the value + return (elem.value || "").replace(rreturn, ""); + + } + + return undefined; + } + + var isFunction = jQuery.isFunction(value); + + return this.each(function(i) { + var self = jQuery(this), val = value; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call(this, i, self.val()); + } + + // Typecast each time if the value is a Function and the appended + // value is therefore different each time. + if ( typeof val === "number" ) { + val += ""; + } + + if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { + this.checked = jQuery.inArray( self.val(), val ) >= 0; + + } else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(val); + + jQuery( "option", this ).each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + this.selectedIndex = -1; + } + + } else { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + // don't set attributes on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery(elem)[name](value); + } + + var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + // Whether we are setting (or getting) + set = value !== undefined; + + // Try to normalize/fix the name + name = notxml && jQuery.props[ name ] || name; + + // Only do all the following if this is a node (faster for style) + if ( elem.nodeType === 1 ) { + // These attributes require special treatment + var special = rspecialurl.test( name ); + + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + + // If applicable, access the attribute via the DOM 0 way + if ( name in elem && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } + + elem[ name ] = value; + } + + // browsers index elements by id/name on forms, give priority to attributes. + if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { + return elem.getAttributeNode( name ).nodeValue; + } + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name === "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); + + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name === "style" ) { + if ( set ) { + elem.style.cssText = "" + value; + } + + return elem.style.cssText; + } + + if ( set ) { + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } + + var attr = !jQuery.support.hrefNormalized && notxml && special ? + // Some attributes require a special call on IE + elem.getAttribute( name, 2 ) : + elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; + } + + // elem is actually elem.style ... set the style + // Using attr for specific style information is now deprecated. Use style instead. + return jQuery.style( elem, name, value ); + } +}); +var rnamespaces = /\.(.*)$/, + fcleanup = function( nm ) { + return nm.replace(/[^\w\s\.\|`]/g, function( ch ) { + return "\\" + ch; + }); + }; + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) { + elem = window; + } + + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery.data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + var events = elemData.events = elemData.events || {}, + eventHandle = elemData.handle, eventHandle; + + if ( !eventHandle ) { + elemData.handle = eventHandle = function() { + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + return typeof jQuery !== "undefined" && !jQuery.event.triggered ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + handleObj.guid = handler.guid; + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = jQuery.event.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push( handleObj ); + + // Keep track of which events have been used, for global triggering + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, pos ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + var ret, type, fn, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + elemData = jQuery.data( elem ), + events = elemData && elemData.events; + + if ( !elemData || !events ) { + return; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)") + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( var j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } + } + + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( var j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); + } + + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + + if ( pos != null ) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + removeEvent( elem, type, elemData.handle ); + } + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem ); + } + } + }, + + // bubbling is internal + trigger: function( event, data, elem /*, bubbling */ ) { + // Event object or event type + var type = event.type || event, + bubbling = arguments[3]; + + if ( !bubbling ) { + event = typeof event === "object" ? + // jQuery.Event object + event[expando] ? event : + // Object literal + jQuery.extend( jQuery.Event(type), event ) : + // Just the event type (string) + jQuery.Event(type); + + if ( type.indexOf("!") >= 0 ) { + event.type = type = type.slice(0, -1); + event.exclusive = true; + } + + // Handle a global trigger + if ( !elem ) { + // Don't bubble custom events when global (to avoid too much overhead) + event.stopPropagation(); + + // Only trigger if we've ever bound an event for it + if ( jQuery.event.global[ type ] ) { + jQuery.each( jQuery.cache, function() { + if ( this.events && this.events[type] ) { + jQuery.event.trigger( event, data, this.handle.elem ); + } + }); + } + } + + // Handle triggering a single element + + // don't do events on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + // Clean up in case it is reused + event.result = undefined; + event.target = elem; + + // Clone the incoming data, if any + data = jQuery.makeArray( data ); + data.unshift( event ); + } + + event.currentTarget = elem; + + // Trigger the event, it is assumed that "handle" is a function + var handle = jQuery.data( elem, "handle" ); + if ( handle ) { + handle.apply( elem, data ); + } + + var parent = elem.parentNode || elem.ownerDocument; + + // Trigger an inline bound script + try { + if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { + if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { + event.result = false; + } + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (e) {} + + if ( !event.isPropagationStopped() && parent ) { + jQuery.event.trigger( event, data, parent, true ); + + } else if ( !event.isDefaultPrevented() ) { + var target = event.target, old, + isClick = jQuery.nodeName(target, "a") && type === "click", + special = jQuery.event.special[ type ] || {}; + + if ( (!special._default || special._default.call( elem, event ) === false) && + !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { + + try { + if ( target[ type ] ) { + // Make sure that we don't accidentally re-trigger the onFOO events + old = target[ "on" + type ]; + + if ( old ) { + target[ "on" + type ] = null; + } + + jQuery.event.triggered = true; + target[ type ](); + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (e) {} + + if ( old ) { + target[ "on" + type ] = old; + } + + jQuery.event.triggered = false; + } + } + }, + + handle: function( event ) { + var all, handlers, namespaces, namespace, events; + + event = arguments[0] = jQuery.event.fix( event || window.event ); + event.currentTarget = this; + + // Namespaced event handlers + all = event.type.indexOf(".") < 0 && !event.exclusive; + + if ( !all ) { + namespaces = event.type.split("."); + event.type = namespaces.shift(); + namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + var events = jQuery.data(this, "events"), handlers = events[ event.type ]; + + if ( events && handlers ) { + // Clone the handlers to prevent manipulation + handlers = handlers.slice(0); + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Filter the functions by class + if ( all || namespace.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, arguments ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + } + + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var doc = document.documentElement, body = document.body; + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) { + event.which = event.charCode || event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( handleObj ) { + jQuery.event.add( this, handleObj.origType, jQuery.extend({}, handleObj, {handler: liveHandler}) ); + }, + + remove: function( handleObj ) { + var remove = true, + type = handleObj.origType.replace(rnamespaces, ""); + + jQuery.each( jQuery.data(this, "events").live || [], function() { + if ( type === this.origType.replace(rnamespaces, "") ) { + remove = false; + return false; + } + }); + + if ( remove ) { + jQuery.event.remove( this, handleObj.origType, liveHandler ); + } + } + + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( this.setInterval ) { + this.onbeforeunload = eventHandle; + } + + return false; + }, + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + } +}; + +var removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + elem.removeEventListener( type, handle, false ); + } : + function( elem, type, handle ) { + elem.detachEvent( "on" + type, handle ); + }; + +jQuery.Event = function( src ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + // Event type + } else { + this.type = src; + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = now(); + + // Mark it as fixed + this[ expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + } + // otherwise set the returnValue property of the original event to false (IE) + e.returnValue = false; + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + // Traverse up the tree + while ( parent && parent !== this ) { + parent = parent.parentNode; + } + + if ( parent !== this ) { + // set the correct event type + event.type = event.data; + + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } + + // assuming we've left the element since we most likely mousedover a xul element + } catch(e) { } +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( this.nodeName.toLowerCase() !== "form" ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + return trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + return trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + + var formElems = /textarea|input|select/i, + + changeFilters, + + getVal = function( elem ) { + var type = elem.type, val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !formElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery.data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery.data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + return jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + click: function( e ) { + var elem = e.target, type = elem.type; + + if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { + return testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = elem.type; + + if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + return testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information/focus[in] is not needed anymore + beforeactivate: function( e ) { + var elem = e.target; + jQuery.data( elem, "_change_data", getVal(elem) ); + } + }, + + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } + + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + } + + return formElems.test( this.nodeName ); + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); + + return formElems.test( this.nodeName ); + } + }; + + changeFilters = jQuery.event.special.change.filters; +} + +function trigger( type, elem, args ) { + args[0].type = type; + return jQuery.event.handle.apply( elem, args ); +} + +// Create "bubbling" focus and blur events +if ( document.addEventListener ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + jQuery.event.special[ fix ] = { + setup: function() { + this.addEventListener( orig, handler, true ); + }, + teardown: function() { + this.removeEventListener( orig, handler, true ); + } + }; + + function handler( e ) { + e = jQuery.event.fix( e ); + e.type = fix; + return jQuery.event.handle.call( this, e ); + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + var handler = name === "one" ? jQuery.proxy( fn, function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }) : fn; + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } + + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + var event = jQuery.Event( type ); + event.preventDefault(); + event.stopPropagation(); + jQuery.event.trigger( event, data, this[0] ); + return event.result; + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, i = 1; + + // link all the functions, so any of them can unbind this click handler + while ( i < args.length ) { + jQuery.proxy( fn, args[ i++ ] ); + } + + return this.click( jQuery.proxy( fn, function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + })); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( type === "focus" || type === "blur" ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + context.each(function(){ + jQuery.event.add( this, liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + }); + + } else { + // unbind live handler + context.unbind( liveConvert( type, selector ), fn ); + } + } + + return this; + } +}); + +function liveHandler( event ) { + var stop, elems = [], selectors = [], args = arguments, + related, match, handleObj, elem, j, i, l, data, + events = jQuery.data( this, "events" ); + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) + if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { + return; + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( match[i].selector === handleObj.selector ) { + elem = match[i].elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + if ( match.handleObj.origHandler.apply( match.elem, args ) === false ) { + stop = false; + break; + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return "live." + (type && type !== "*" ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&"); +} + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( fn ) { + return fn ? this.bind( name, fn ) : this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + +// Prevent memory leaks in IE +// Window isn't included so as not to unbind existing unload events +// More info: +// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ +if ( window.attachEvent && !window.addEventListener ) { + window.attachEvent("onunload", function() { + for ( var id in jQuery.cache ) { + if ( jQuery.cache[ id ].handle ) { + // Try/Catch is to handle iframes being unloaded, see #4280 + try { + jQuery.event.remove( jQuery.cache[ id ].handle.elem ); + } catch(e) {} + } + } + }); +} +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var filter = Expr.filter[ type ], found, item, left = match[1]; + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = part.toLowerCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = part.toLowerCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = part.toLowerCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + return match[1].toLowerCase(); + }, + CHILD: function(match){ + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 === i; + }, + eq: function(elem, i, match){ + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } else { + Sizzle.error( "Syntax error, unrecognized expression: " + name ); + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + if ( type === "first" ) { + return true; + } + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first === 0 ) { + return diff === 0; + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, function(all, num){ + return "\\" + (num - 0 + 1); + })); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.compareDocumentPosition ? -1 : 1; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.sourceIndex ? -1 : 1; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.ownerDocument ? -1 : 1; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +function getText( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += getText( elem.childNodes ); + } + } + + return ret; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE + })(); +} + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return !!(a.compareDocumentPosition(b) & 16); +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = getText; +jQuery.isXMLDoc = isXML; +jQuery.contains = contains; + +return; + +window.Sizzle = Sizzle; + +})(); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + slice = Array.prototype.slice; + +// Implement the identical functionality for filter and not +var winnow = function( elements, qualifier, keep ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var ret = this.pushStack( "", "find", selector ), length = 0; + + for ( var i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( var n = length; n < ret.length; n++ ) { + for ( var r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && jQuery.filter( selector, this ).length > 0; + }, + + closest: function( selectors, context ) { + if ( jQuery.isArray( selectors ) ) { + var ret = [], cur = this[0], match, matches = {}, selector; + + if ( cur && selectors.length ) { + for ( var i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[selector] ) { + matches[selector] = jQuery.expr.match.POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[selector]; + + if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { + ret.push({ selector: selector, elem: cur }); + delete matches[selector]; + } + } + cur = cur.parentNode; + } + } + + return ret; + } + + var pos = jQuery.expr.match.POS.test( selectors ) ? + jQuery( selectors, context || this.context ) : null; + + return this.map(function( i, cur ) { + while ( cur && cur.ownerDocument && cur !== context ) { + if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selectors) ) { + return cur; + } + cur = cur.parentNode; + } + return null; + }); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + if ( !elem || typeof elem === "string" ) { + return jQuery.inArray( this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery( elem ) : this.parent().children() ); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context || this.context ) : + jQuery.makeArray( selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call(arguments).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], cur = elem[dir]; + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /(<([\w:]+)[^>]*?)\/>/g, + rselfClosing = /^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i, + rtagName = /<([\w:]+)/, + rtbody = /"; + }, + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + col: [ 2, "", "
" ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

SyntaxHighlighter Highlight Lines Test

+

+
+

+
    + + + +
    +
    + + + + + + + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/theme_tests.html b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/theme_tests.html new file mode 100755 index 0000000..da07ecf --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/theme_tests.html @@ -0,0 +1,134 @@ + + + + + SyntaxHighlighter Theme Tests + + + + + +
    + + + + + + + + + diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/webrick.rb b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/webrick.rb new file mode 100755 index 0000000..0b3c93c --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/webrick.rb @@ -0,0 +1,11 @@ +require 'webrick' +include WEBrick + +s = HTTPServer.new( + :Port => 2010, + :DocumentRoot => Dir::pwd +) +s.mount('/sh/scripts', WEBrick::HTTPServlet::FileHandler, '../scripts') +s.mount('/sh/styles', WEBrick::HTTPServlet::FileHandler, '../styles') +trap('INT') { s.stop } +s.start diff --git a/clipper/Documentation/Scripts/SyntaxHighlighter/tests/webrick.sh b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/webrick.sh new file mode 100755 index 0000000..7e249f8 --- /dev/null +++ b/clipper/Documentation/Scripts/SyntaxHighlighter/tests/webrick.sh @@ -0,0 +1,2 @@ +#!/bin/sh +ruby webrick.rb diff --git a/clipper/Documentation/Scripts/bootstrap.js b/clipper/Documentation/Scripts/bootstrap.js new file mode 100755 index 0000000..d779d80 --- /dev/null +++ b/clipper/Documentation/Scripts/bootstrap.js @@ -0,0 +1,2 @@ +// This file may be used to bootstrap the DOM via JavaScript... +SyntaxHighlighter.all(); diff --git a/clipper/Documentation/Scripts/expandable.js b/clipper/Documentation/Scripts/expandable.js new file mode 100755 index 0000000..49b2aec --- /dev/null +++ b/clipper/Documentation/Scripts/expandable.js @@ -0,0 +1,190 @@ +/* + * Expandable list implementation. + * by David Lindquist + * See: + * http://www.gazingus.org/html/DOM-Scripted_Lists_Revisited.html + * Modifies lists so that sublists can be hidden and shown by means of + * a switch. The switch is a node inserted into the DOM tree as the + * first child of the list item containing the sublist. + */ + +// The script will only be applied to lists containing this class name, +// e.g.: . +var CLASS_NAME = "expandable"; + +// This value is the assumed initial display style for a sublist when it cannot +// be determined by other means. See below. +var DEFAULT_DISPLAY = "none"; + +// The namespace to use when using this script in an XML context. +var XMLNS = "http://www.w3.org/1999/xhtml"; + +// The beginning of the title text for the switch when the sublist is collapsed. +var CLOSED_PREFIX = "Expand list: "; + +// The beginning of the title text for the switch when the sublist is expanded. +var OPENED_PREFIX = "Collapse list: "; + +/******************************************************************************/ + +// Returns a copy of a string with leading and trailing whitespace removed. +String.prototype.trim = function() { + return this.replace(/^\s+/, "").replace(/\s+$/, ""); +} + +// Walks the DOM tree starting at a given root element. Returns an +// array of nodes of the specified type and conforming to the criteria +// of the given filter function. The filter should return a boolean. +function getNodesByType(root, type, filter) { + var node = root; + var nodes = []; + var next; + + while (node != null) { + if (node.hasChildNodes()) + node = node.firstChild; + else if (node != root && null != (next = node.nextSibling)) + node = next; + else { + next = null; + for ( ; node != root; node = node.parentNode) { + next = node.nextSibling; + if (next != null) break; + } + node = next; + } + if (node != null && node.nodeType == type && filter(node)) + nodes.push(node); + } + return nodes; +} + +// Simulates the innerText property of IE and other browsers. +// Mozilla/Firefox need this. +function getInnerText(node) { + if (node == null || node.nodeType != 1) + return; + var text = ""; + var textnodes = getNodesByType(node, 3, function() { return true; }); + for (var i = 0; i < textnodes.length; i++) + text += textnodes[i].data; + return text; +} + +function initExpandableLists() { + if (!document.getElementsByTagName) return; + + // Top-level function to accommodate browsers that do not register + // a click event when a link is activated by the keyboard. + switchNode = function(id) { + var node = document.getElementById(id); + if (node && /^switch /.test(node.className)) node.onclick(); + } + + // Top-level function to be assigned as the event handler for the + // switch. This could have been bound to the handler as a closure, + // but closures are associated with memory leak problems in IE. + actuate = function() { + var sublist = this.parentNode.getElementsByTagName("ul")[0] || + this.parentNode.getElementsByTagName("ol")[0]; + if (sublist.style.display == "block") { + sublist.style.display = "none"; + this.firstChild.data = "+"; + this.className = "switch off"; + this.title = this.title.replace(OPENED_PREFIX, CLOSED_PREFIX); + } else { + sublist.style.display = "block"; + this.firstChild.data = "-"; + this.className = "switch on"; + this.title = this.title.replace(CLOSED_PREFIX, OPENED_PREFIX); + } + return false; + } + + // Create switch node from which the others will be cloned. + if (typeof document.createElementNS == "function") + var template = document.createElementNS(XMLNS, "a"); + else + var template = document.createElement("a"); + template.appendChild(document.createTextNode(" ")); + + var list, i = 0, j = 0; + var pattern = new RegExp("(^| )" + CLASS_NAME + "( |$)"); + + while ((list = document.getElementsByTagName("ul")[i++]) || + (list = document.getElementsByTagName("ol")[j++])) + { + // Only lists with the given class name are processed. + if (pattern.test(list.className) == false) continue; + + var item, k = 0; + while ((item = list.getElementsByTagName("li")[k++])) { + var sublist = item.getElementsByTagName("ul")[0] || + item.getElementsByTagName("ol")[0]; + // No sublist under this list item. Skip it. + if (sublist == null) continue; + + // Attempt to determine initial display style of the + // sublist so the proper symbol is used. + var symbol; + switch (sublist.style.display) { + case "none" : symbol = "+"; break; + case "block": symbol = "-"; break; + default: + var display = DEFAULT_DISPLAY; + if (sublist.currentStyle) { + display = sublist.currentStyle.display; + } else if (document.defaultView && + document.defaultView.getComputedStyle && + document.defaultView.getComputedStyle(sublist, "")) + { + var view = document.defaultView; + var computed = view.getComputedStyle(sublist, ""); + display = computed.getPropertyValue("display"); + } + symbol = (display == "none") ? "+" : "-"; + // Explicitly set the display style to make sure it is + // set for the next read. If it is somehow the empty + // string, use the default value from the (X)HTML DTD. + sublist.style.display = display || "block"; + break; + } + + // This bit attempts to extract some text from the first + // child node of the list item to append to the title + // attribute of the switch. + var child = item.firstChild; + var text = ""; + while (child) { + if (child.nodeType == 3 && "" != child.data.trim()) { + text = child.data; + break; + } else if (child.nodeType == 1 && + !/^[ou]l$/i.test(child.tagName)) + { + text = child.innerText || getInnerText(child); + break; + } + child = child.nextSibling; + } + + var actuator = template.cloneNode(true); + // a reasonably unique ID + var uid = "switch" + i + "-" + j + "-" + k; + actuator.id = uid; + actuator.href = "javascript:switchNode('" + uid + "')"; + actuator.className = "switch " + ((symbol == "+") ? "off" : "on"); + actuator.title = ((symbol == "+") + ? CLOSED_PREFIX : OPENED_PREFIX) + text.trim(); + actuator.firstChild.data = symbol; + actuator.onclick = actuate; + item.insertBefore(actuator, item.firstChild); + } + } +} + +// Props to Simon Willison: +// http://simon.incutio.com/archive/2004/05/26/addLoadEvent +var oldhandler = window.onload; +window.onload = (typeof oldhandler == "function") + ? function() { oldhandler(); initExpandableLists(); } : initExpandableLists; diff --git a/clipper/Documentation/Scripts/menu_data.js b/clipper/Documentation/Scripts/menu_data.js new file mode 100755 index 0000000..4382a9e --- /dev/null +++ b/clipper/Documentation/Scripts/menu_data.js @@ -0,0 +1,21 @@ +td_1 = "Overview" +td_1_1 = "Library Overview" +url_1_1 = "Overview/_Body.htm" +td_1_2 = "Changes" +url_1_2 = "Overview/Changes.htm" +td_1_3 = "Examples" +url_1_3 = "Overview/Example.htm" +td_1_4 = "FAQ" +url_1_4 = "Overview/FAQ.htm" +td_1_5 = "Rounding" +url_1_5 = "Overview/Rounding.htm" +td_1_6 = "Compatibility with Prior Versions" +url_1_6 = "Overview/Compatibility with Prior Versions.htm" +td_1_7 = "License" +url_1_7 = "Overview/License.htm" + +td_2 = "Internet" +td_2_1 = "Clipper at SourceForge" +url_2_1 = "%http://sourceforge.net/projects/polyclipping/" +td_2_2 = "Clipper at angusj.com" +url_2_2 = "%http://www.angusj.com/delphi/clipper.php" diff --git a/clipper/Documentation/Scripts/menu_script.js b/clipper/Documentation/Scripts/menu_script.js new file mode 100755 index 0000000..dc9d70d --- /dev/null +++ b/clipper/Documentation/Scripts/menu_script.js @@ -0,0 +1,130 @@ + +////////////////////Please leave this notice//////////////////// +// +// DropDown Menu 1.0 +// By Evgeny Novikov (java@aladin.ru) +// http://java.skyteam.ru +// It works only with IE5.0(++) and Netscape6.0(++) +// Free to use! +// +////////////////////Last modified 2002-03-05//////////////////// + +// Modify following four lines to customize your menu +var tdColor = "#FFFFFF"; // menu item text color +var tdBgColor = "#6060A0"; // menu item background color +var hlColor = "#000000"; // highlight text color +var hlBgColor = "#9595BD"; // highlight background color +// After change, modify same values in your *.css file + +var md = 250; +var ti = -1; +var oTd = new Object; +oTd = null; + +function doMenu(td) { + clearTimeout(ti); + td.style.backgroundColor = hlBgColor; + td.style.color = hlColor; + var i; + var sT = ""; + var tda = new Array(); + tda = td.id.split("_"); + if (oTd != null) { + var tdo = new Array(); + tdo = oTd.id.split("_"); + for (i = 1; i < tdo.length; i++) { + sT += "_" + tdo[i]; + if (tdo[i] != tda[i]) { + document.getElementById("td" + sT).style.backgroundColor = tdBgColor; + document.getElementById("td" + sT).style.color = tdColor; + if (document.getElementById("tbl" + sT) != null) + document.getElementById("tbl" + sT).style.visibility = "hidden"; + } + } + } + oTd = td; + sT = "tbl"; + for (i = 1; i < tda.length; i++) + sT += "_" + tda[i]; + if (document.getElementById(sT) != null) + document.getElementById(sT).style.visibility = "visible"; + +} + +function clearMenu() { + if (oTd != null) { + var tdo = new Array(); + tdo = oTd.id.split("_"); + var sT = ""; + for (var i = 1; i < tdo.length; i++) { + sT += "_" + tdo[i]; + document.getElementById("td" + sT).style.backgroundColor = tdBgColor; + document.getElementById("td" + sT).style.color = tdColor; + if (document.getElementById("tbl" + sT) != null) + document.getElementById("tbl" + sT).style.visibility = "hidden"; + } + oTd = null; + } +} + +function runMenu(strURL) { + if (strURL.charAt(0) == '%') + window.open(strURL.substr(1)); + else + location.href = strURL; +} + +var tt = ""; +var sT = ""; +var pT = new Array(); +var tA = new Array(); + +function getCoord(st) { + tA = st.split("_"); + if (tA.length > 2) { + tA = tA.slice(0,-1); + tt = tA.join("_"); + return (document.getElementById("tbl" + tt).offsetTop + document.getElementById("td" + st).offsetTop - 1) + "px;left:" + + (document.getElementById("tbl" + tt).offsetLeft + document.getElementById("td" + st).offsetWidth - 2) + "px\">"; + } + return (document.getElementById("mainmenu").offsetTop + document.getElementById("td" + st).offsetHeight - 10) + "px;left:" + + (document.getElementById("mainmenu").offsetLeft + document.getElementById("td" + st).offsetLeft + 5) + "px\">"; +} + +function isDefined(varname) { + return eval("typeof(" + varname + ") != \"undefined\""); +} + +var sH = ""; +var p = 0; +var j = 0; + +while (isDefined("td_" + ++j)) { + sH += ""; + if (isDefined("td_" + j + "_1")) + pT[p++] = "_" + j; +} + +sH += ""; +sH += "
    " : ">"; + sH += eval("td_" + j) + "
    "; +document.write(sH); + +for (var q = 0; typeof(pT[q]) != "undefined"; q++) { + sT = pT[q]; + sH = ""; + j = 0; + sH += "" : ">"; + sH += eval("td" + sT + "_" + j) + ""; + if (isDefined("td" + sT + "_" + j + "_1")) + pT[p++] = sT + "_" + j; + } + sH += "
    "; + document.write(sH); +} +document.getElementById("mainmenu").style.visibility = "visible"; + diff --git a/clipper/Documentation/Styles/Default.css b/clipper/Documentation/Styles/Default.css new file mode 100755 index 0000000..74b4d96 --- /dev/null +++ b/clipper/Documentation/Styles/Default.css @@ -0,0 +1,265 @@ +p.Body { + margin-left: 1cm; + margin-right: 0.5cm; +} + +p.Hierarchy { + margin-top: 0; + margin-left : 1cm; + margin-bottom : 0; + font-size : 90%; +} + +.EmptyRef { + font-weight: bold; +} + +.API { + color: #700070; + font-style: italic; +} + +.Code { + margin-left: 1cm; + margin-right: 1cm; + margin-top: 0px; + margin-bottom: 0px; +} + +p.Decl { + font-family: "Courier New", Courier, monospace; + background-color: #E0E0E0; + padding-top: 3pt; + padding-right: 4pt; + padding-left: 6pt; + padding-bottom: 3pt; + margin-left: 1cm; + margin-top: 0px; + margin-right: 0.5cm; + margin-bottom: 0px; +} + +p.Decl2 { + font-family: "Courier New", Courier, monospace; + background-color: #99FF99; + padding-top: 3pt; + padding-right: 4pt; + padding-left: 6pt; + padding-bottom: 3pt; + margin-left: 1cm; + margin-top: 0px; + margin-right: 0.5cm; + margin-bottom: 0px; + font-size : 100%; +} + +p.Decl3 { + font-family: "Courier New", Courier, monospace; + background-color: #FFBBCC; + padding-top: 3pt; + padding-right: 4pt; + padding-left: 6pt; + padding-bottom: 3pt; + margin-left: 1cm; + margin-top: 0px; + margin-right: 0.5cm; + margin-bottom: 0px; + font-size : 100%; +} + +p.Decl4 { + font-family: Arial, Helvetica, sans-serif; + padding-top: 3pt; + padding-right: 4pt; + padding-left: 6pt; + padding-bottom: 3pt; + margin-left: 1cm; + margin-top: 0px; + margin-right: 0.5cm; + margin-bottom: 0px; +} + +body { + font-family: Verdana, Tahoma, serif; + font-size: 80%; + margin-left : 2px; + margin-top: 2px; + margin-right : 2px; + margin-top : 2px; +} + +p { + margin-top: 5pt; + margin-bottom: 5pt; +} + +h1 { + font-size: 150%; + margin-top: 5pt; + margin-bottom: 16pt; + margin-left : 0.25cm; +} + +h2 { + font-size: 100%; + margin-bottom: 4pt; + margin-top: 16px; + margin-left: 0.25cm; + padding-left: 0px; +} + +table { + border: none; + margin-left: 1cm; + border-color: #FFFFFF; + margin-right: 0.5cm; +} + +th { + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 80%; + background-color: #99FF99; + padding-right: 6pt; + padding-left: 6pt; +} + +td { + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 80%; + background-color: #F0F0F0; + padding-right: 4pt; + padding-left: 4pt; +} + +td.White { + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 80%; + background-color: #FFFFFF; + padding-right: 4pt; + padding-left: 4pt; +} + +table.Banner { + color: white; + font-size: 9pt; + background-color: #9595bd; + margin: 1px; + padding: 1px, 10px; + width: 100%; + cellpadding: 0; + cellspacing: 0; +} + +td.Banner { + background-color: #6060A0; + font-size: 9pt; + margin: 1px; + padding: 1px 10px; +} +a:link { color: blue; text-decoration: none } +a:visited { color: blue; text-decoration: none } +a:hover { color: black; text-decoration: none; background-color: #d0d0ff } +table.Home { + background-color: #FFFFFF; + border: #FFFFFF none; + margin: 1px; + padding: 0px; + border-color: #FFFFFF; + /*width: 100%;*/ +} +table.menu { color: white; background-color: #6060a0; visibility: hidden; cursor: pointer; margin: 1px; padding: 1px 10px; position: static; border: 1px solid; border-color: #272758 #656596 #272758 #272758 } +table.menudrop { color: white; background-color: #6060a0; visibility: hidden; cursor: pointer; margin: 10px; padding: 5px 10px; position: absolute; border-left: 1px solid #444475 } +table.menu td { font-size: 8pt; background-color: #6060a0; white-space: nowrap; margin: 1px; padding: 1px 10px; border: #FFFFFF none; } +table.menudrop td { font-size: 8pt; background-color: #6060a0; white-space: nowrap; margin: 1px; padding: 5px 10px; border: #FFFFFF none; } +td.Home { + background-color: #FFFFFF; + padding: 1px 0px; + margin: 1px; +} +a:link.Banner { + text-decoration: none; + color: white; +} + +a:visited.Banner { + text-decoration: none; + color : white; +} + +a:hover.Banner { + background-color: #6060A0; + text-decoration: none; + color: white; +} + +a.Bold { + font-weight: bold; +} + +li { + list-style-position: outside; + list-style-type: square; + margin-top: 3pt; margin-bottom: 3pt; + margin-left: 12pt; +} + +ul { + margin-top: 8pt; + margin-bottom : 8pt; +} + +.Comment { + font-style: italic; + color: #206020; +} + +.CPPNumeric { + color: blue; +} + +.CSharp { + color: #2B91AF; +} + +img { + margin-top: 0px; + margin-right: 0px; + margin-bottom: 0px; + margin-left: 0px; + border-top-width: 0px; + border-right-width: 0px; + border-bottom-width: 0px; + border-left-width: 0px; + padding-top: 0px; + padding-right: 0px; + padding-bottom: 0px; + padding-left: 0px; +} + +span.sub { + font-size: 80%; + vertical-align: sub; +} + +.Tree { + margin-top: 0px; + margin-bottom: 0px; + margin-left: 1cm; + padding-top: 0px; + padding-bottom: 0px; +} + +span.Menu { + font-weight: bold; + color: #402000 +} + +p.Copyright {font-size: 7pt; color: #808090; text-align: center; font-family: Tahoma, Arial, sans-serif} + +.pascalcode { + margin-left: 1cm; + font-family: "Courier New", Courier, mono; +} +#mainmenu { position: static } + +.maroon {color: #990000;} \ No newline at end of file diff --git a/clipper/Documentation/clipper.chm b/clipper/Documentation/clipper.chm new file mode 100755 index 0000000..73b64f2 Binary files /dev/null and b/clipper/Documentation/clipper.chm differ diff --git a/clipper/Documentation/index.htm b/clipper/Documentation/index.htm new file mode 100755 index 0000000..06e4946 --- /dev/null +++ b/clipper/Documentation/index.htm @@ -0,0 +1,12 @@ + + + + + +Redirect + + +If the documentation doesn't open automatically, click here. + + diff --git a/clipper/Flash/AS3_flash_readme.txt b/clipper/Flash/AS3_flash_readme.txt new file mode 100755 index 0000000..da54dfd --- /dev/null +++ b/clipper/Flash/AS3_flash_readme.txt @@ -0,0 +1,2 @@ +An AS3 Flash port written by Chris Denham: +https://github.com/ChrisDenham/PolygonClipper.AS3 diff --git a/clipper/Haskell/Haskell_readme.txt b/clipper/Haskell/Haskell_readme.txt new file mode 100755 index 0000000..a341b6d --- /dev/null +++ b/clipper/Haskell/Haskell_readme.txt @@ -0,0 +1,4 @@ + +An Haskell module written by Chetan Taralekar +that wraps the Clipper library can be downloaded from: +http://hackage.haskell.org/package/clipper diff --git a/clipper/License.txt b/clipper/License.txt new file mode 100755 index 0000000..51acabf --- /dev/null +++ b/clipper/License.txt @@ -0,0 +1,29 @@ +The Clipper code library, the "Software" (that includes Delphi, C++ & C# +source code, accompanying samples and documentation), has been released +under the following license, terms and conditions: + +Boost Software License - Version 1.0 - August 17th, 2003 +http://www.boost.org/LICENSE_1_0.txt + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + diff --git a/clipper/Matlab/matlab_readme.txt b/clipper/Matlab/matlab_readme.txt new file mode 100755 index 0000000..3950354 --- /dev/null +++ b/clipper/Matlab/matlab_readme.txt @@ -0,0 +1,4 @@ + +A Matlab wrapper for Clipper by Emmett (emmettl@u.washington.edu) can be downloaded from: + +http://www.mathworks.com/matlabcentral/fileexchange/36241 \ No newline at end of file diff --git a/clipper/README b/clipper/README new file mode 100755 index 0000000..7f4d513 --- /dev/null +++ b/clipper/README @@ -0,0 +1,288 @@ +============================================================ +Clipper Change Log +============================================================ +v5.1.0 (17 February 2013) +* Update: ExPolygons has been replaced with the PolyTree & + PolyNode classes to more fully represent the parent-child + relationships of the polygons returned by Clipper. +* Added: New CleanPolygon and CleanPolygons functions. +* Bugfix: Another orientation bug fixed. + +v5.0.2 - 30 December 2012 +* Bugfix: Significant fixes in and tidy of the internal + Int128 class (which is used only when polygon coordinate + values are greater than ±0x3FFFFFFF (~1.07e9)). +* Update: The Area algorithm has been updated and is faster. +* Update: Documentation updates. The newish but undocumented + 'CheckInputs' parameter of the OffsetPolygons function has been + renamed 'AutoFix' and documented too. The comments on rounding + have also been improved (ie clearer and expanded). + +v4.10.0 - 25 December 2012 +* Bugfix: Orientation bugs should now be resolved (finally!). +* Bugfix: Bug in Int128 class + +v4.9.8 - 2 December 2012 +* Bugfix: Further fixes to rare Orientation bug. + +v4.9.7 - 29 November 2012 +* Bugfix: Bug that very rarely returned the wrong polygon + orientation. +* Bugfix: Obscure bug affecting OffsetPolygons when using + jtRound for the JoinType parameter and when polygons also + contain very large coordinate values (> +/-100000000000). + +v4.9.6 - 9 November 2012 +* Bugfix: Another obscure bug related to joining polygons. + +v4.9.4 - 2 November 2012 +* Bugfix: Bugs in Int128 class occasionally causing + wrong orientations. +* Bugfix: Further fixes related to joining polygons. + +v4.9.0 - 9 October 2012 +* Bugfix: Obscure bug related to joining polygons. + +v4.8.9 - 25 September 2012 +* Bugfix: Obscure bug related to precision of intersections. + +v4.8.8 - 30 August 2012 +* Bugfix: Fixed bug in OffsetPolygons function introduced in + version 4.8.5. + +v4.8.7 - 24 August 2012 +* Bugfix: ReversePolygon function in C++ translation was broken. +* Bugfix: Two obscure bugs affecting orientation fixed too. + +v4.8.6 - 11 August 2012 +* Bugfix: Potential for memory overflow errors when using + ExPolygons structure. +* Bugfix: The polygon coordinate range has been reduced to + +/- 0x3FFFFFFFFFFFFFFF (4.6e18). +* Update: ReversePolygons function was misnamed ReversePoints in C++. +* Update: SimplifyPolygon function now takes a PolyFillType parameter. + +v4.8.5 - 15 July 2012 +* Bugfix: Potential for memory overflow errors in OffsetPolygons(). + +v4.8.4 - 1 June 2012 +* Bugfix: Another obscure bug affecting ExPolygons structure. + +v4.8.3 - 27 May 2012 +* Bugfix: Obscure bug causing incorrect removal of a vertex. + +v4.8.2 - 21 May 2012 +* Bugfix: Obscure bug could cause an exception when using + ExPolygon structure. + +v4.8.1 - 12 May 2012 +* Update: Cody tidy and minor bug fixes. + +v4.8.0 - 30 April 2012 +* Bugfix: Occasional errors in orientation fixed. +* Update: Added notes on rounding to the documentation. + +v4.7.6 - 11 April 2012 +* Fixed a bug in Orientation function (affecting C# translations only). +* Minor documentation update. + +v4.7.5 - 28 March 2012 +* Bugfix: Fixed a recently introduced bug that occasionally caused an + unhandled exception in C++ and C# translations. + +v4.7.4 - 15 March 2012 +* Bugfix: Another minor bugfix. + +v4.7.2 - 4 March 2012 +* Bugfix: Fixed bug introduced in ver 4.7 which sometimes caused + an exception if ExPolygon structure was passed to Clipper's + Execute method. + +v4.7.1 - 3 March 2012 +* Bugfix: Rare crash when JoinCommonEdges joined polygons that + 'cancelled' each other. +* Bugfix: Clipper's internal Orientation method occasionally + returned wrong result. +* Update: Improved C# code (thanks to numerous excellent suggestions + from David Piepgrass) + +v4.7 - 10 February 2012 +* Improved the joining of output polygons sharing a common edge. + +v4.6.6 - 3 February 2012 +* Bugfix: Another obscure bug occasionally causing incorrect + polygon orientation. + +v4.6.5 - 17 January 2012 +* Bugfix: Obscure bug occasionally causing incorrect hole + assignment in ExPolygon structure. + +v4.6.4 - 8 November 2011 +* Added: SimplifyPolygon and SimplifyPolygons functions. + +v4.6.3 - 11 November 2011 +* Bugfix: Fixed another minor mitering bug in OffsetPolygons. + +v4.6.2 - 10 November 2011 +* Bugfix: Fixed a rare bug in the orientation of polygons + returned by Clipper's Execute() method. +* Bugfix: Previous update introduced a mitering bug in the + OffsetPolygons function. + +v4.6 - 29 October 2011 +* Added: Support for Positive and Negative polygon fill + types (in addition to the EvenOdd and NonZero fill types). +* Bugfix: The OffsetPolygons function was generating the + occasional artefact when 'shrinking' polygons. + +v4.5.5 - 8 October 2011 +* Bugfix: Fixed an obscure bug in Clipper's JoinCommonEdges + method. +* Update: Replaced IsClockwise function with Orientation + function. The orientation issues affecting OffsetPolygons + should now be finally resolved. +* Change: The Area function once again returns a signed value. + +v4.5.1 - 28 September 2011 +* Deleted: The UseFullCoordinateRange property has been + deleted since integer range is now managed implicitly. +* BugFix: Minor bug in OffsetPolygon mitering. +* Change: C# JoinType enum moved from Clipper class to + ClipperLib namespace. +* Change: The Area function now returns the absolute area + (irrespective of orientation). +* Change: The IsClockwise function now requires a second + parameter - YAxisPositiveUpward - to accommodate displays + with Y-axis oriented in either direction + +v4.4.4 - 10 September 2011 +* Change: Deleted jtButt from JoinType (used by the + OffsetPolygons function). +* BugFix: Fixed another minor bug in OffsetPolygons function. +* Update: Further improvements to the help file + +v4.4.3 - 29 August 2011 +* BugFix: fixed a minor rounding issue in OffsetPolygons + function (affected C++ & C# translations). +* BugFix: fixed a minor bug in OffsetPolygons' function + declaration (affected C++ translation only). +* Change: 'clipper' namespace changed to 'ClipperLib' + namespace in both C++ and C# code to remove the ambiguity + between the Clipper class and the namespace. (This also + required numerous updates to the accompanying demos.) + +v4.4.2 - 26 August 2011 +* BugFix: minor bugfixes in Clipper. +* Update: the OffsetPolygons function has been significantly + improved by offering 4 different join styles. + +v4.4.0 - 6 August 2011 +* BugFix: A number of minor bugs have been fixed that mostly + affected the new ExPolygons structure. + +v4.3.0 - 17 June 2011 +* New: ExPolygons structure that explicitly associates 'hole' + polygons with their 'outer' container polygons. +* New: Execute method overloaded so the solution parameter + can now be either Polygons or ExPolygons. +* BugFix: Fixed a rare bug in solution polygons orientation. + +v4.2.8 - 21 May 2011 +* Update: JoinCommonEdges() improved once more. +* BugFix: Several minor bugs fixed. + +v4.2.6 - 1 May 2011 +* Bugfix: minor bug in SlopesEqual function. +* Update: Merging of output polygons sharing common edges + has been significantly inproved + +v4.2.4 - 26 April 2011 + Input polygon coordinates can now contain the full range of + signed 64bit integers (ie +/-9,223,372,036,854,775,807). This + means that floating point values can be converted to and from + Clipper's 64bit integer coordinates structure (IntPoint) and + still retain a precision of up to 18 decimal places. However, + since the large-integer math that supports this expanded range + imposes a small cost on performance (~15%), a new property + UseFullCoordinateRange has been added to the Clipper class to + allow users the choice of whether or not to use this expanded + coordinate range. If this property is disabled, coordinate values + are restricted to +/-1,500,000,000. + +v4.2 - 12 April 2011 + JoinCommonEdges() code significantly improved plus other minor + improvements. + +v4.1.2 - 9 April 2011 +* Update: Minor code tidy. +* Bugfix: Possible endless loop in JoinCommonEdges() in clipper.pas. + +v4.1.1 - 8 April 2011 +* Update: All polygon coordinates are now stored as 64bit integers + (though they're still restricted to range -1.5e9 to +1.5e9 pending + the inclusion of code supporting 64bit math). +* Change: AddPolygon and AddPolygons methods now return boolean + values. +* Bugfix: Bug in JoinCommonEdges() caused potential endless loop. +* Bugfix: Bug in IsClockwise(). (C++ code only) + +v4.0 - 5 April 2011 +* Clipper 4 is a major rewrite of earlier versions. The biggest + change is that floating point values are no longer used, + except for the storing of edge slope values. The main benefit + of this is the issue of numerical robustness has been + addressed. Due to other major code improvements Clipper v4 + is approximately 40% faster than Clipper v3. +* The AddPolyPolygon method has been renamed to AddPolygons. +* The IgnoreOrientation property has been removed. +* The clipper_misc library has been merged back into the + main clipper library. + +v3.1.0 - 17 February 2011 +* Bugfix: Obscure bug in TClipperBase.SetDx method that caused + problems with very small edges ( edges <1/1000th pixel in size). + +v3.0.3 - 9 February 2011 +* Bugfix: Significant bug, but only in C# code. +* Update: Minor refactoring. + +v3.0 - 31 January 2011 +* Update: Major rewrite of the portion of code that calculates + the output polygons' orientation. +* Update: Help file significantly improved. +* Change: Renamed ForceOrientation property to IgnoreOrientation. + If the orientation of output polygons is not important, or can + be managed separately, clipping routines can be sped up by about + 60% by setting IgnoreOrientation to true. Defaults to false. +* Change: The OffsetPolygon and Area functions have been moved to + the new unit - clipper_misc. + +2.99 - 15 January 2011 +* Bugfix: Obscure bug in AddPolygon method could cause an endless loop. + +2.8 - 20 November 2010 +* Updated: Output polygons which previously shared a common + edge are now merged. +* Changed: The orientation of outer polygons is now clockwise + when the display's Y axis is positive downwards (as is + typical for most Windows applications). Inner polygons + (holes) have the opposite orientation. +* Added: Support module for Cairo Graphics Library (with demo). +* Updated: C# and C++ demos. + +2.522 - 15 October 2010 +* Added C# translation (thanks to Olivier Lejeune) and + a link to Ruby bindings (thanks to Mike Owens). + +2.0 - 30 July 2010 +* Clipper now clips using both the Even-Odd (alternate) and + Non-Zero (winding) polygon filling rules. (Previously Clipper + assumed the Even-Odd rule for polygon filling.) + +1.4c - 16 June 2010 +* Added C++ support for AGG graphics library + +1.2s - 2 June 2010 +* Added C++ translation of clipper.pas + +1.0 - 9 May 2010 \ No newline at end of file diff --git a/clipper/clipper.cpp b/clipper/clipper.cpp new file mode 100755 index 0000000..1042052 --- /dev/null +++ b/clipper/clipper.cpp @@ -0,0 +1,3469 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 5.1.0 * +* Date : 1 February 2013 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2013 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +/******************************************************************************* +* * +* This is a translation of the Delphi Clipper library and the naming style * +* used has retained a Delphi flavour. * +* * +*******************************************************************************/ + +#include "clipper.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +static long64 const loRange = 0x3FFFFFFF; +static long64 const hiRange = 0x3FFFFFFFFFFFFFFFLL; +static double const pi = 3.141592653589793238; +enum Direction { dRightToLeft, dLeftToRight }; + +#define HORIZONTAL (-1.0E+40) +#define TOLERANCE (1.0e-20) +#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) +#define NEAR_EQUAL(a, b) NEAR_ZERO((a) - (b)) + +inline long64 Abs(long64 val) +{ + return val < 0 ? -val : val; +} + +//------------------------------------------------------------------------------ +// PolyTree methods ... +//------------------------------------------------------------------------------ + +void PolyTree::Clear() +{ + for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) + delete AllNodes[i]; + AllNodes.resize(0); + Childs.resize(0); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyTree::GetFirst() +{ + if (Childs.size() > 0) + return Childs[0]; + else + return 0; +} +//------------------------------------------------------------------------------ + +int PolyTree::Total() +{ + return AllNodes.size(); +} + +//------------------------------------------------------------------------------ +// PolyNode methods ... +//------------------------------------------------------------------------------ + + +int PolyNode::ChildCount() +{ + return Childs.size(); +} +//------------------------------------------------------------------------------ + +void PolyNode::AddChild(PolyNode& child) +{ + unsigned cnt = Childs.size(); + Childs.push_back(&child); + child.Parent = this; + child.Index = cnt; +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNext() +{ + if (Childs.size() > 0) + return Childs[0]; + else + return GetNextSiblingUp(); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNextSiblingUp() +{ + if (!Parent) //protects against PolyTree.GetNextSiblingUp() + return 0; + else if (Index == Parent->Childs.size() - 1) + return Parent->GetNextSiblingUp(); + else + return Parent->Childs[Index + 1]; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsHole() +{ + bool result = true; + PolyNode* node = Parent; + while (node) + { + result = !result; + node = node->Parent; + } + return result; +} + +//------------------------------------------------------------------------------ +// Int128 class (enables safe math on signed 64bit integers) +// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1 +// Int128 val2((long64)9223372036854775807); +// Int128 val3 = val1 * val2; +// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) +//------------------------------------------------------------------------------ + +class Int128 +{ + public: + + ulong64 lo; + long64 hi; + + Int128(long64 _lo = 0) + { + lo = (ulong64)_lo; + if (_lo < 0) hi = -1; else hi = 0; + } + + + Int128(const Int128 &val): lo(val.lo), hi(val.hi){} + + Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} + + long64 operator = (const long64 &val) + { + lo = (ulong64)val; + if (val < 0) hi = -1; else hi = 0; + return val; + } + + bool operator == (const Int128 &val) const + {return (hi == val.hi && lo == val.lo);} + + bool operator != (const Int128 &val) const + { return !(*this == val);} + + bool operator > (const Int128 &val) const + { + if (hi != val.hi) + return hi > val.hi; + else + return lo > val.lo; + } + + bool operator < (const Int128 &val) const + { + if (hi != val.hi) + return hi < val.hi; + else + return lo < val.lo; + } + + bool operator >= (const Int128 &val) const + { return !(*this < val);} + + bool operator <= (const Int128 &val) const + { return !(*this > val);} + + Int128& operator += (const Int128 &rhs) + { + hi += rhs.hi; + lo += rhs.lo; + if (lo < rhs.lo) hi++; + return *this; + } + + Int128 operator + (const Int128 &rhs) const + { + Int128 result(*this); + result+= rhs; + return result; + } + + Int128& operator -= (const Int128 &rhs) + { + *this += -rhs; + return *this; + } + + Int128 operator - (const Int128 &rhs) const + { + Int128 result(*this); + result -= rhs; + return result; + } + + Int128 operator-() const //unary negation + { + if (lo == 0) + return Int128(-hi,0); + else + return Int128(~hi,~lo +1); + } + + Int128 operator/ (const Int128 &rhs) const + { + if (rhs.lo == 0 && rhs.hi == 0) + throw "Int128 operator/: divide by zero"; + + bool negate = (rhs.hi < 0) != (hi < 0); + Int128 dividend = *this; + Int128 divisor = rhs; + if (dividend.hi < 0) dividend = -dividend; + if (divisor.hi < 0) divisor = -divisor; + + if (divisor < dividend) + { + Int128 result = Int128(0); + Int128 cntr = Int128(1); + while (divisor.hi >= 0 && !(divisor > dividend)) + { + divisor.hi <<= 1; + if ((long64)divisor.lo < 0) divisor.hi++; + divisor.lo <<= 1; + + cntr.hi <<= 1; + if ((long64)cntr.lo < 0) cntr.hi++; + cntr.lo <<= 1; + } + divisor.lo >>= 1; + if ((divisor.hi & 1) == 1) + divisor.lo |= 0x8000000000000000LL; + divisor.hi = (ulong64)divisor.hi >> 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000LL; + cntr.hi >>= 1; + + while (cntr.hi != 0 || cntr.lo != 0) + { + if (!(dividend < divisor)) + { + dividend -= divisor; + result.hi |= cntr.hi; + result.lo |= cntr.lo; + } + divisor.lo >>= 1; + if ((divisor.hi & 1) == 1) + divisor.lo |= 0x8000000000000000LL; + divisor.hi >>= 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000LL; + cntr.hi >>= 1; + } + if (negate) result = -result; + return result; + } + else if (rhs.hi == this->hi && rhs.lo == this->lo) + return Int128(1); + else + return Int128(0); + } + + double AsDouble() const + { + const double shift64 = 18446744073709551616.0; //2^64 + if (hi < 0) + { + if (lo == 0) return (double)hi * shift64; + else return -(double)(~lo + ~hi * shift64); + } + else + return (double)(lo + hi * shift64); + } +}; + +Int128 Int128Mul (long64 lhs, long64 rhs) +{ + bool negate = (lhs < 0) != (rhs < 0); + + if (lhs < 0) lhs = -lhs; + ulong64 int1Hi = ulong64(lhs) >> 32; + ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); + + if (rhs < 0) rhs = -rhs; + ulong64 int2Hi = ulong64(rhs) >> 32; + ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); + + //nb: see comments in clipper.pas + ulong64 a = int1Hi * int2Hi; + ulong64 b = int1Lo * int2Lo; + ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + Int128 tmp; + tmp.hi = long64(a + (c >> 32)); + tmp.lo = long64(c << 32); + tmp.lo += long64(b); + if (tmp.lo < b) tmp.hi++; + if (negate) tmp = -tmp; + return tmp; +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +bool FullRangeNeeded(const Polygon &pts) +{ + bool result = false; + for (Polygon::size_type i = 0; i < pts.size(); ++i) + { + if (Abs(pts[i].X) > hiRange || Abs(pts[i].Y) > hiRange) + throw "Coordinate exceeds range bounds."; + else if (Abs(pts[i].X) > loRange || Abs(pts[i].Y) > loRange) + result = true; + } + return result; +} +//------------------------------------------------------------------------------ + +bool Orientation(const Polygon &poly) +{ + return Area(poly) >= 0; +} +//------------------------------------------------------------------------------ + +inline bool PointsEqual( const IntPoint &pt1, const IntPoint &pt2) +{ + return ( pt1.X == pt2.X && pt1.Y == pt2.Y ); +} +//------------------------------------------------------------------------------ + +double Area(const Polygon &poly) +{ + int highI = (int)poly.size() -1; + if (highI < 2) return 0; + + if (FullRangeNeeded(poly)) { + Int128 a; + a = Int128Mul(poly[highI].X + poly[0].X, poly[0].Y - poly[highI].Y); + for (int i = 1; i <= highI; ++i) + a += Int128Mul(poly[i - 1].X + poly[i].X, poly[i].Y - poly[i -1].Y); + return a.AsDouble() / 2; + } + else + { + double a; + a = ((double)poly[highI].X + poly[0].X) * ((double)poly[0].Y - poly[highI].Y); + for (int i = 1; i <= highI; ++i) + a += ((double)poly[i - 1].X + poly[i].X) * ((double)poly[i].Y - poly[i - 1].Y); + return a / 2; + } +} +//------------------------------------------------------------------------------ + +double Area(const OutRec &outRec, bool UseFullInt64Range) +{ + OutPt *op = outRec.pts; + if (!op) return 0; + if (UseFullInt64Range) { + Int128 a(0); + do { + a += Int128Mul(op->pt.X + op->prev->pt.X, op->prev->pt.Y - op->pt.Y); + op = op->next; + } while (op != outRec.pts); + return a.AsDouble() / 2; + } + else + { + double a = 0; + do { + a = a + (op->pt.X + op->prev->pt.X) * (op->prev->pt.Y - op->pt.Y); + op = op->next; + } while (op != outRec.pts); + return a / 2; + } +} +//------------------------------------------------------------------------------ + +bool PointIsVertex(const IntPoint &pt, OutPt *pp) +{ + OutPt *pp2 = pp; + do + { + if (PointsEqual(pp2->pt, pt)) return true; + pp2 = pp2->next; + } + while (pp2 != pp); + return false; +} +//------------------------------------------------------------------------------ + +bool PointInPolygon(const IntPoint &pt, OutPt *pp, bool UseFullInt64Range) +{ + OutPt *pp2 = pp; + bool result = false; + if (UseFullInt64Range) { + do + { + if ((((pp2->pt.Y <= pt.Y) && (pt.Y < pp2->prev->pt.Y)) || + ((pp2->prev->pt.Y <= pt.Y) && (pt.Y < pp2->pt.Y))) && + Int128(pt.X - pp2->pt.X) < + Int128Mul(pp2->prev->pt.X - pp2->pt.X, pt.Y - pp2->pt.Y) / + Int128(pp2->prev->pt.Y - pp2->pt.Y)) + result = !result; + pp2 = pp2->next; + } + while (pp2 != pp); + } + else + { + do + { + if ((((pp2->pt.Y <= pt.Y) && (pt.Y < pp2->prev->pt.Y)) || + ((pp2->prev->pt.Y <= pt.Y) && (pt.Y < pp2->pt.Y))) && + (pt.X < (pp2->prev->pt.X - pp2->pt.X) * (pt.Y - pp2->pt.Y) / + (pp2->prev->pt.Y - pp2->pt.Y) + pp2->pt.X )) result = !result; + pp2 = pp2->next; + } + while (pp2 != pp); + } + return result; +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(TEdge &e1, TEdge &e2, bool UseFullInt64Range) +{ + if (UseFullInt64Range) + return Int128Mul(e1.deltaY, e2.deltaX) == Int128Mul(e1.deltaX, e2.deltaY); + else return e1.deltaY * e2.deltaX == e1.deltaX * e2.deltaY; +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, bool UseFullInt64Range) +{ + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); + else return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) +{ + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); + else return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); +} +//------------------------------------------------------------------------------ + +double GetDx(const IntPoint pt1, const IntPoint pt2) +{ + return (pt1.Y == pt2.Y) ? + HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); +} +//--------------------------------------------------------------------------- + +void SetDx(TEdge &e) +{ + e.deltaX = (e.xtop - e.xbot); + e.deltaY = (e.ytop - e.ybot); + + if (e.deltaY == 0) e.dx = HORIZONTAL; + else e.dx = (double)(e.deltaX) / e.deltaY; +} +//--------------------------------------------------------------------------- + +void SwapSides(TEdge &edge1, TEdge &edge2) +{ + EdgeSide side = edge1.side; + edge1.side = edge2.side; + edge2.side = side; +} +//------------------------------------------------------------------------------ + +void SwapPolyIndexes(TEdge &edge1, TEdge &edge2) +{ + int outIdx = edge1.outIdx; + edge1.outIdx = edge2.outIdx; + edge2.outIdx = outIdx; +} +//------------------------------------------------------------------------------ + +inline long64 Round(double val) +{ + return (val < 0) ? static_cast(val - 0.5) : static_cast(val + 0.5); +} +//------------------------------------------------------------------------------ + +long64 TopX(TEdge &edge, const long64 currentY) +{ + return ( currentY == edge.ytop ) ? + edge.xtop : edge.xbot + Round(edge.dx *(currentY - edge.ybot)); +} +//------------------------------------------------------------------------------ + +bool IntersectPoint(TEdge &edge1, TEdge &edge2, + IntPoint &ip, bool UseFullInt64Range) +{ + double b1, b2; + if (SlopesEqual(edge1, edge2, UseFullInt64Range)) return false; + else if (NEAR_ZERO(edge1.dx)) + { + ip.X = edge1.xbot; + if (NEAR_EQUAL(edge2.dx, HORIZONTAL)) + { + ip.Y = edge2.ybot; + } else + { + b2 = edge2.ybot - (edge2.xbot / edge2.dx); + ip.Y = Round(ip.X / edge2.dx + b2); + } + } + else if (NEAR_ZERO(edge2.dx)) + { + ip.X = edge2.xbot; + if (NEAR_EQUAL(edge1.dx, HORIZONTAL)) + { + ip.Y = edge1.ybot; + } else + { + b1 = edge1.ybot - (edge1.xbot / edge1.dx); + ip.Y = Round(ip.X / edge1.dx + b1); + } + } else + { + b1 = edge1.xbot - edge1.ybot * edge1.dx; + b2 = edge2.xbot - edge2.ybot * edge2.dx; + double q = (b2-b1) / (edge1.dx - edge2.dx); + ip.Y = Round(q); + if (std::fabs(edge1.dx) < std::fabs(edge2.dx)) + ip.X = Round(edge1.dx * q + b1); + else + ip.X = Round(edge2.dx * q + b2); + } + + if (ip.Y < edge1.ytop || ip.Y < edge2.ytop) + { + if (edge1.ytop > edge2.ytop) + { + ip.X = edge1.xtop; + ip.Y = edge1.ytop; + return TopX(edge2, edge1.ytop) < edge1.xtop; + } else + { + ip.X = edge2.xtop; + ip.Y = edge2.ytop; + return TopX(edge1, edge2.ytop) > edge2.xtop; + } + } + else + return true; +} +//------------------------------------------------------------------------------ + +void ReversePolyPtLinks(OutPt *pp) +{ + if (!pp) return; + OutPt *pp1, *pp2; + pp1 = pp; + do { + pp2 = pp1->next; + pp1->next = pp1->prev; + pp1->prev = pp2; + pp1 = pp2; + } while( pp1 != pp ); +} +//------------------------------------------------------------------------------ + +void DisposeOutPts(OutPt*& pp) +{ + if (pp == 0) return; + pp->prev->next = 0; + while( pp ) + { + OutPt *tmpPp = pp; + pp = pp->next; + delete tmpPp ; + } +} +//------------------------------------------------------------------------------ + +void InitEdge(TEdge *e, TEdge *eNext, + TEdge *ePrev, const IntPoint &pt, PolyType polyType) +{ + std::memset( e, 0, sizeof( TEdge )); + + e->next = eNext; + e->prev = ePrev; + e->xcurr = pt.X; + e->ycurr = pt.Y; + if (e->ycurr >= e->next->ycurr) + { + e->xbot = e->xcurr; + e->ybot = e->ycurr; + e->xtop = e->next->xcurr; + e->ytop = e->next->ycurr; + e->windDelta = 1; + } else + { + e->xtop = e->xcurr; + e->ytop = e->ycurr; + e->xbot = e->next->xcurr; + e->ybot = e->next->ycurr; + e->windDelta = -1; + } + SetDx(*e); + e->polyType = polyType; + e->outIdx = -1; +} +//------------------------------------------------------------------------------ + +inline void SwapX(TEdge &e) +{ + //swap horizontal edges' top and bottom x's so they follow the natural + //progression of the bounds - ie so their xbots will align with the + //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] + e.xcurr = e.xtop; + e.xtop = e.xbot; + e.xbot = e.xcurr; +} +//------------------------------------------------------------------------------ + +void SwapPoints(IntPoint &pt1, IntPoint &pt2) +{ + IntPoint tmp = pt1; + pt1 = pt2; + pt2 = tmp; +} +//------------------------------------------------------------------------------ + +bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, + IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) +{ + //precondition: segments are colinear. + if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) + { + if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); + if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); + if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; + return pt1.X < pt2.X; + } else + { + if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b); + if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b); + if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; + return pt1.Y > pt2.Y; + } +} +//------------------------------------------------------------------------------ + +bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) +{ + OutPt *p = btmPt1->prev; + while (PointsEqual(p->pt, btmPt1->pt) && (p != btmPt1)) p = p->prev; + double dx1p = std::fabs(GetDx(btmPt1->pt, p->pt)); + p = btmPt1->next; + while (PointsEqual(p->pt, btmPt1->pt) && (p != btmPt1)) p = p->next; + double dx1n = std::fabs(GetDx(btmPt1->pt, p->pt)); + + p = btmPt2->prev; + while (PointsEqual(p->pt, btmPt2->pt) && (p != btmPt2)) p = p->prev; + double dx2p = std::fabs(GetDx(btmPt2->pt, p->pt)); + p = btmPt2->next; + while (PointsEqual(p->pt, btmPt2->pt) && (p != btmPt2)) p = p->next; + double dx2n = std::fabs(GetDx(btmPt2->pt, p->pt)); + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); +} +//------------------------------------------------------------------------------ + +OutPt* GetBottomPt(OutPt *pp) +{ + OutPt* dups = 0; + OutPt* p = pp->next; + while (p != pp) + { + if (p->pt.Y > pp->pt.Y) + { + pp = p; + dups = 0; + } + else if (p->pt.Y == pp->pt.Y && p->pt.X <= pp->pt.X) + { + if (p->pt.X < pp->pt.X) + { + dups = 0; + pp = p; + } else + { + if (p->next != pp && p->prev != pp) dups = p; + } + } + p = p->next; + } + if (dups) + { + //there appears to be at least 2 vertices at bottomPt so ... + while (dups != p) + { + if (!FirstIsBottomPt(p, dups)) pp = dups; + dups = dups->next; + while (!PointsEqual(dups->pt, pp->pt)) dups = dups->next; + } + } + return pp; +} +//------------------------------------------------------------------------------ + +bool FindSegment(OutPt* &pp, IntPoint &pt1, IntPoint &pt2) +{ + //outPt1 & outPt2 => the overlap segment (if the function returns true) + if (!pp) return false; + OutPt* pp2 = pp; + IntPoint pt1a = pt1, pt2a = pt2; + do + { + if (SlopesEqual(pt1a, pt2a, pp->pt, pp->prev->pt, true) && + SlopesEqual(pt1a, pt2a, pp->pt, true) && + GetOverlapSegment(pt1a, pt2a, pp->pt, pp->prev->pt, pt1, pt2)) + return true; + pp = pp->next; + } + while (pp != pp2); + return false; +} +//------------------------------------------------------------------------------ + +bool Pt3IsBetweenPt1AndPt2(const IntPoint pt1, + const IntPoint pt2, const IntPoint pt3) +{ + if (PointsEqual(pt1, pt3) || PointsEqual(pt2, pt3)) return true; + else if (pt1.X != pt2.X) return (pt1.X < pt3.X) == (pt3.X < pt2.X); + else return (pt1.Y < pt3.Y) == (pt3.Y < pt2.Y); +} +//------------------------------------------------------------------------------ + +OutPt* InsertPolyPtBetween(OutPt* p1, OutPt* p2, const IntPoint pt) +{ + if (p1 == p2) throw "JoinError"; + OutPt* result = new OutPt; + result->pt = pt; + if (p2 == p1->next) + { + p1->next = result; + p2->prev = result; + result->next = p2; + result->prev = p1; + } else + { + p2->next = result; + p1->prev = result; + result->next = p1; + result->prev = p2; + } + return result; +} + +//------------------------------------------------------------------------------ +// ClipperBase class methods ... +//------------------------------------------------------------------------------ + +ClipperBase::ClipperBase() //constructor +{ + m_MinimaList = 0; + m_CurrentLM = 0; + m_UseFullRange = true; +} +//------------------------------------------------------------------------------ + +ClipperBase::~ClipperBase() //destructor +{ + Clear(); +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPolygon( const Polygon &pg, PolyType polyType) +{ + int len = (int)pg.size(); + if (len < 3) return false; + + Polygon p(len); + p[0] = pg[0]; + int j = 0; + + long64 maxVal; + if (m_UseFullRange) maxVal = hiRange; else maxVal = loRange; + + for (int i = 0; i < len; ++i) + { + if (Abs(pg[i].X) > maxVal || Abs(pg[i].Y) > maxVal) + { + if (Abs(pg[i].X) > hiRange || Abs(pg[i].Y) > hiRange) + throw "Coordinate exceeds range bounds"; + maxVal = hiRange; + m_UseFullRange = true; + } + + if (i == 0 || PointsEqual(p[j], pg[i])) continue; + else if (j > 0 && SlopesEqual(p[j-1], p[j], pg[i], m_UseFullRange)) + { + if (PointsEqual(p[j-1], pg[i])) j--; + } else j++; + p[j] = pg[i]; + } + if (j < 2) return false; + + len = j+1; + while (len > 2) + { + //nb: test for point equality before testing slopes ... + if (PointsEqual(p[j], p[0])) j--; + else if (PointsEqual(p[0], p[1]) || + SlopesEqual(p[j], p[0], p[1], m_UseFullRange)) + p[0] = p[j--]; + else if (SlopesEqual(p[j-1], p[j], p[0], m_UseFullRange)) j--; + else if (SlopesEqual(p[0], p[1], p[2], m_UseFullRange)) + { + for (int i = 2; i <= j; ++i) p[i-1] = p[i]; + j--; + } + else break; + len--; + } + if (len < 3) return false; + + //create a new edge array ... + TEdge *edges = new TEdge [len]; + m_edges.push_back(edges); + + //convert vertices to a double-linked-list of edges and initialize ... + edges[0].xcurr = p[0].X; + edges[0].ycurr = p[0].Y; + InitEdge(&edges[len-1], &edges[0], &edges[len-2], p[len-1], polyType); + for (int i = len-2; i > 0; --i) + InitEdge(&edges[i], &edges[i+1], &edges[i-1], p[i], polyType); + InitEdge(&edges[0], &edges[1], &edges[len-1], p[0], polyType); + + //reset xcurr & ycurr and find 'eHighest' (given the Y axis coordinates + //increase downward so the 'highest' edge will have the smallest ytop) ... + TEdge *e = &edges[0]; + TEdge *eHighest = e; + do + { + e->xcurr = e->xbot; + e->ycurr = e->ybot; + if (e->ytop < eHighest->ytop) eHighest = e; + e = e->next; + } + while ( e != &edges[0]); + + //make sure eHighest is positioned so the following loop works safely ... + if (eHighest->windDelta > 0) eHighest = eHighest->next; + if (NEAR_EQUAL(eHighest->dx, HORIZONTAL)) eHighest = eHighest->next; + + //finally insert each local minima ... + e = eHighest; + do { + e = AddBoundsToLML(e); + } + while( e != eHighest ); + return true; +} +//------------------------------------------------------------------------------ + +void ClipperBase::InsertLocalMinima(LocalMinima *newLm) +{ + if( ! m_MinimaList ) + { + m_MinimaList = newLm; + } + else if( newLm->Y >= m_MinimaList->Y ) + { + newLm->next = m_MinimaList; + m_MinimaList = newLm; + } else + { + LocalMinima* tmpLm = m_MinimaList; + while( tmpLm->next && ( newLm->Y < tmpLm->next->Y ) ) + tmpLm = tmpLm->next; + newLm->next = tmpLm->next; + tmpLm->next = newLm; + } +} +//------------------------------------------------------------------------------ + +TEdge* ClipperBase::AddBoundsToLML(TEdge *e) +{ + //Starting at the top of one bound we progress to the bottom where there's + //a local minima. We then go to the top of the next bound. These two bounds + //form the left and right (or right and left) bounds of the local minima. + e->nextInLML = 0; + e = e->next; + for (;;) + { + if (NEAR_EQUAL(e->dx, HORIZONTAL)) + { + //nb: proceed through horizontals when approaching from their right, + // but break on horizontal minima if approaching from their left. + // This ensures 'local minima' are always on the left of horizontals. + if (e->next->ytop < e->ytop && e->next->xbot > e->prev->xbot) break; + if (e->xtop != e->prev->xbot) SwapX(*e); + e->nextInLML = e->prev; + } + else if (e->ycurr == e->prev->ycurr) break; + else e->nextInLML = e->prev; + e = e->next; + } + + //e and e.prev are now at a local minima ... + LocalMinima* newLm = new LocalMinima; + newLm->next = 0; + newLm->Y = e->prev->ybot; + + if ( NEAR_EQUAL(e->dx, HORIZONTAL) ) //horizontal edges never start a left bound + { + if (e->xbot != e->prev->xbot) SwapX(*e); + newLm->leftBound = e->prev; + newLm->rightBound = e; + } else if (e->dx < e->prev->dx) + { + newLm->leftBound = e->prev; + newLm->rightBound = e; + } else + { + newLm->leftBound = e; + newLm->rightBound = e->prev; + } + newLm->leftBound->side = esLeft; + newLm->rightBound->side = esRight; + InsertLocalMinima( newLm ); + + for (;;) + { + if ( e->next->ytop == e->ytop && !NEAR_EQUAL(e->next->dx, HORIZONTAL) ) break; + e->nextInLML = e->next; + e = e->next; + if ( NEAR_EQUAL(e->dx, HORIZONTAL) && e->xbot != e->prev->xtop) SwapX(*e); + } + return e->next; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPolygons(const Polygons &ppg, PolyType polyType) +{ + bool result = false; + for (Polygons::size_type i = 0; i < ppg.size(); ++i) + if (AddPolygon(ppg[i], polyType)) result = true; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Clear() +{ + DisposeLocalMinimaList(); + for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) delete [] m_edges[i]; + m_edges.clear(); + m_UseFullRange = false; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Reset() +{ + m_CurrentLM = m_MinimaList; + if( !m_CurrentLM ) return; //ie nothing to process + + //reset all edges ... + LocalMinima* lm = m_MinimaList; + while( lm ) + { + TEdge* e = lm->leftBound; + while( e ) + { + e->xcurr = e->xbot; + e->ycurr = e->ybot; + e->side = esLeft; + e->outIdx = -1; + e = e->nextInLML; + } + e = lm->rightBound; + while( e ) + { + e->xcurr = e->xbot; + e->ycurr = e->ybot; + e->side = esRight; + e->outIdx = -1; + e = e->nextInLML; + } + lm = lm->next; + } +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeLocalMinimaList() +{ + while( m_MinimaList ) + { + LocalMinima* tmpLm = m_MinimaList->next; + delete m_MinimaList; + m_MinimaList = tmpLm; + } + m_CurrentLM = 0; +} +//------------------------------------------------------------------------------ + +void ClipperBase::PopLocalMinima() +{ + if( ! m_CurrentLM ) return; + m_CurrentLM = m_CurrentLM->next; +} +//------------------------------------------------------------------------------ + +IntRect ClipperBase::GetBounds() +{ + IntRect result; + LocalMinima* lm = m_MinimaList; + if (!lm) + { + result.left = result.top = result.right = result.bottom = 0; + return result; + } + result.left = lm->leftBound->xbot; + result.top = lm->leftBound->ybot; + result.right = lm->leftBound->xbot; + result.bottom = lm->leftBound->ybot; + while (lm) + { + if (lm->leftBound->ybot > result.bottom) + result.bottom = lm->leftBound->ybot; + TEdge* e = lm->leftBound; + for (;;) { + TEdge* bottomE = e; + while (e->nextInLML) + { + if (e->xbot < result.left) result.left = e->xbot; + if (e->xbot > result.right) result.right = e->xbot; + e = e->nextInLML; + } + if (e->xbot < result.left) result.left = e->xbot; + if (e->xbot > result.right) result.right = e->xbot; + if (e->xtop < result.left) result.left = e->xtop; + if (e->xtop > result.right) result.right = e->xtop; + if (e->ytop < result.top) result.top = e->ytop; + + if (bottomE == lm->leftBound) e = lm->rightBound; + else break; + } + lm = lm->next; + } + return result; +} + + +//------------------------------------------------------------------------------ +// TClipper methods ... +//------------------------------------------------------------------------------ + +Clipper::Clipper() : ClipperBase() //constructor +{ + m_Scanbeam = 0; + m_ActiveEdges = 0; + m_SortedEdges = 0; + m_IntersectNodes = 0; + m_ExecuteLocked = false; + m_UseFullRange = false; + m_ReverseOutput = false; +} +//------------------------------------------------------------------------------ + +Clipper::~Clipper() //destructor +{ + Clear(); + DisposeScanbeamList(); +} +//------------------------------------------------------------------------------ + +void Clipper::Clear() +{ + if (m_edges.size() == 0) return; //avoids problems with ClipperBase destructor + DisposeAllPolyPts(); + ClipperBase::Clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeScanbeamList() +{ + while ( m_Scanbeam ) { + Scanbeam* sb2 = m_Scanbeam->next; + delete m_Scanbeam; + m_Scanbeam = sb2; + } +} +//------------------------------------------------------------------------------ + +void Clipper::Reset() +{ + ClipperBase::Reset(); + m_Scanbeam = 0; + m_ActiveEdges = 0; + m_SortedEdges = 0; + DisposeAllPolyPts(); + LocalMinima* lm = m_MinimaList; + while (lm) + { + InsertScanbeam(lm->Y); + InsertScanbeam(lm->leftBound->ytop); + lm = lm->next; + } +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, Polygons &solution, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + m_ExecuteLocked = true; + solution.resize(0); + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = false; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult(solution); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree& polytree, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + m_ExecuteLocked = true; + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = true; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult2(polytree); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::FixHoleLinkage(OutRec &outRec) +{ + //skip OutRecs that (a) contain outermost polygons or + //(b) already have the correct owner/child linkage ... + if (!outRec.FirstLeft || + (outRec.isHole != outRec.FirstLeft->isHole && + outRec.FirstLeft->pts)) return; + + OutRec* orfl = outRec.FirstLeft; + while (orfl && ((orfl->isHole == outRec.isHole) || !orfl->pts)) + orfl = orfl->FirstLeft; + outRec.FirstLeft = orfl; +} +//------------------------------------------------------------------------------ + +bool Clipper::ExecuteInternal() +{ + bool succeeded; + try { + Reset(); + if (!m_CurrentLM ) return true; + long64 botY = PopScanbeam(); + do { + InsertLocalMinimaIntoAEL(botY); + ClearHorzJoins(); + ProcessHorizontals(); + long64 topY = PopScanbeam(); + succeeded = ProcessIntersections(botY, topY); + if (!succeeded) break; + ProcessEdgesAtTopOfScanbeam(topY); + botY = topY; + } while( m_Scanbeam ); + } + catch(...) { + succeeded = false; + } + + if (succeeded) + { + //tidy up output polygons and fix orientations where necessary ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->pts) continue; + FixupOutPolygon(*outRec); + if (!outRec->pts) continue; + + if ((outRec->isHole ^ m_ReverseOutput) == (Area(*outRec, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec->pts); + } + + if (m_Joins.size() > 0) JoinCommonEdges(); + } + + ClearJoins(); + ClearHorzJoins(); + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::InsertScanbeam(const long64 Y) +{ + if( !m_Scanbeam ) + { + m_Scanbeam = new Scanbeam; + m_Scanbeam->next = 0; + m_Scanbeam->Y = Y; + } + else if( Y > m_Scanbeam->Y ) + { + Scanbeam* newSb = new Scanbeam; + newSb->Y = Y; + newSb->next = m_Scanbeam; + m_Scanbeam = newSb; + } else + { + Scanbeam* sb2 = m_Scanbeam; + while( sb2->next && ( Y <= sb2->next->Y ) ) sb2 = sb2->next; + if( Y == sb2->Y ) return; //ie ignores duplicates + Scanbeam* newSb = new Scanbeam; + newSb->Y = Y; + newSb->next = sb2->next; + sb2->next = newSb; + } +} +//------------------------------------------------------------------------------ + +long64 Clipper::PopScanbeam() +{ + long64 Y = m_Scanbeam->Y; + Scanbeam* sb2 = m_Scanbeam; + m_Scanbeam = m_Scanbeam->next; + delete sb2; + return Y; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeAllPolyPts(){ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + DisposeOutRec(i); + m_PolyOuts.clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeOutRec(PolyOutList::size_type index) +{ + OutRec *outRec = m_PolyOuts[index]; + if (outRec->pts) DisposeOutPts(outRec->pts); + delete outRec; + m_PolyOuts[index] = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::SetWindingCount(TEdge &edge) +{ + TEdge *e = edge.prevInAEL; + //find the edge of the same polytype that immediately preceeds 'edge' in AEL + while ( e && e->polyType != edge.polyType ) e = e->prevInAEL; + if ( !e ) + { + edge.windCnt = edge.windDelta; + edge.windCnt2 = 0; + e = m_ActiveEdges; //ie get ready to calc windCnt2 + } else if ( IsEvenOddFillType(edge) ) + { + //EvenOdd filling ... + edge.windCnt = 1; + edge.windCnt2 = e->windCnt2; + e = e->nextInAEL; //ie get ready to calc windCnt2 + } else + { + //nonZero, Positive or Negative filling ... + if ( e->windCnt * e->windDelta < 0 ) + { + if (Abs(e->windCnt) > 1) + { + if (e->windDelta * edge.windDelta < 0) edge.windCnt = e->windCnt; + else edge.windCnt = e->windCnt + edge.windDelta; + } else + edge.windCnt = e->windCnt + e->windDelta + edge.windDelta; + } else + { + if ( Abs(e->windCnt) > 1 && e->windDelta * edge.windDelta < 0) + edge.windCnt = e->windCnt; + else if ( e->windCnt + edge.windDelta == 0 ) + edge.windCnt = e->windCnt; + else edge.windCnt = e->windCnt + edge.windDelta; + } + edge.windCnt2 = e->windCnt2; + e = e->nextInAEL; //ie get ready to calc windCnt2 + } + + //update windCnt2 ... + if ( IsEvenOddAltFillType(edge) ) + { + //EvenOdd filling ... + while ( e != &edge ) + { + edge.windCnt2 = (edge.windCnt2 == 0) ? 1 : 0; + e = e->nextInAEL; + } + } else + { + //nonZero, Positive or Negative filling ... + while ( e != &edge ) + { + edge.windCnt2 += e->windDelta; + e = e->nextInAEL; + } + } +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddFillType(const TEdge& edge) const +{ + if (edge.polyType == ptSubject) + return m_SubjFillType == pftEvenOdd; else + return m_ClipFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const +{ + if (edge.polyType == ptSubject) + return m_ClipFillType == pftEvenOdd; else + return m_SubjFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsContributing(const TEdge& edge) const +{ + PolyFillType pft, pft2; + if (edge.polyType == ptSubject) + { + pft = m_SubjFillType; + pft2 = m_ClipFillType; + } else + { + pft = m_ClipFillType; + pft2 = m_SubjFillType; + } + + switch(pft) + { + case pftEvenOdd: + case pftNonZero: + if (Abs(edge.windCnt) != 1) return false; + break; + case pftPositive: + if (edge.windCnt != 1) return false; + break; + default: //pftNegative + if (edge.windCnt != -1) return false; + } + + switch(m_ClipType) + { + case ctIntersection: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.windCnt2 != 0); + case pftPositive: + return (edge.windCnt2 > 0); + default: + return (edge.windCnt2 < 0); + } + case ctUnion: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.windCnt2 == 0); + case pftPositive: + return (edge.windCnt2 <= 0); + default: + return (edge.windCnt2 >= 0); + } + case ctDifference: + if (edge.polyType == ptSubject) + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.windCnt2 == 0); + case pftPositive: + return (edge.windCnt2 <= 0); + default: + return (edge.windCnt2 >= 0); + } + else + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.windCnt2 != 0); + case pftPositive: + return (edge.windCnt2 > 0); + default: + return (edge.windCnt2 < 0); + } + default: + return true; + } +} +//------------------------------------------------------------------------------ + +void Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt) +{ + TEdge *e, *prevE; + if( NEAR_EQUAL(e2->dx, HORIZONTAL) || ( e1->dx > e2->dx ) ) + { + AddOutPt( e1, pt ); + e2->outIdx = e1->outIdx; + e1->side = esLeft; + e2->side = esRight; + e = e1; + if (e->prevInAEL == e2) + prevE = e2->prevInAEL; + else + prevE = e->prevInAEL; + } else + { + AddOutPt( e2, pt ); + e1->outIdx = e2->outIdx; + e1->side = esRight; + e2->side = esLeft; + e = e2; + if (e->prevInAEL == e1) + prevE = e1->prevInAEL; + else + prevE = e->prevInAEL; + } + if (prevE && prevE->outIdx >= 0 && + (TopX(*prevE, pt.Y) == TopX(*e, pt.Y)) && + SlopesEqual(*e, *prevE, m_UseFullRange)) + AddJoin(e, prevE, -1, -1); +} +//------------------------------------------------------------------------------ + +void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt) +{ + AddOutPt( e1, pt ); + if( e1->outIdx == e2->outIdx ) + { + e1->outIdx = -1; + e2->outIdx = -1; + } + else if (e1->outIdx < e2->outIdx) + AppendPolygon(e1, e2); + else + AppendPolygon(e2, e1); +} +//------------------------------------------------------------------------------ + +void Clipper::AddEdgeToSEL(TEdge *edge) +{ + //SEL pointers in PEdge are reused to build a list of horizontal edges. + //However, we don't need to worry about order with horizontal edge processing. + if( !m_SortedEdges ) + { + m_SortedEdges = edge; + edge->prevInSEL = 0; + edge->nextInSEL = 0; + } + else + { + edge->nextInSEL = m_SortedEdges; + edge->prevInSEL = 0; + m_SortedEdges->prevInSEL = edge; + m_SortedEdges = edge; + } +} +//------------------------------------------------------------------------------ + +void Clipper::CopyAELToSEL() +{ + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while ( e ) + { + e->prevInSEL = e->prevInAEL; + e->nextInSEL = e->nextInAEL; + e = e->nextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::AddJoin(TEdge *e1, TEdge *e2, int e1OutIdx, int e2OutIdx) +{ + JoinRec* jr = new JoinRec; + if (e1OutIdx >= 0) + jr->poly1Idx = e1OutIdx; else + jr->poly1Idx = e1->outIdx; + jr->pt1a = IntPoint(e1->xcurr, e1->ycurr); + jr->pt1b = IntPoint(e1->xtop, e1->ytop); + if (e2OutIdx >= 0) + jr->poly2Idx = e2OutIdx; else + jr->poly2Idx = e2->outIdx; + jr->pt2a = IntPoint(e2->xcurr, e2->ycurr); + jr->pt2b = IntPoint(e2->xtop, e2->ytop); + m_Joins.push_back(jr); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearJoins() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + delete m_Joins[i]; + m_Joins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::AddHorzJoin(TEdge *e, int idx) +{ + HorzJoinRec* hj = new HorzJoinRec; + hj->edge = e; + hj->savedIdx = idx; + m_HorizJoins.push_back(hj); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearHorzJoins() +{ + for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); i++) + delete m_HorizJoins[i]; + m_HorizJoins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertLocalMinimaIntoAEL(const long64 botY) +{ + while( m_CurrentLM && ( m_CurrentLM->Y == botY ) ) + { + TEdge* lb = m_CurrentLM->leftBound; + TEdge* rb = m_CurrentLM->rightBound; + + InsertEdgeIntoAEL( lb ); + InsertScanbeam( lb->ytop ); + InsertEdgeIntoAEL( rb ); + + if (IsEvenOddFillType(*lb)) + { + lb->windDelta = 1; + rb->windDelta = 1; + } + else + { + rb->windDelta = -lb->windDelta; + } + SetWindingCount( *lb ); + rb->windCnt = lb->windCnt; + rb->windCnt2 = lb->windCnt2; + + if( NEAR_EQUAL(rb->dx, HORIZONTAL) ) + { + //nb: only rightbounds can have a horizontal bottom edge + AddEdgeToSEL( rb ); + InsertScanbeam( rb->nextInLML->ytop ); + } + else + InsertScanbeam( rb->ytop ); + + if( IsContributing(*lb) ) + AddLocalMinPoly( lb, rb, IntPoint(lb->xcurr, m_CurrentLM->Y) ); + + //if any output polygons share an edge, they'll need joining later ... + if (rb->outIdx >= 0 && NEAR_EQUAL(rb->dx, HORIZONTAL)) + { + for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) + { + IntPoint pt, pt2; //returned by GetOverlapSegment() but unused here. + HorzJoinRec* hj = m_HorizJoins[i]; + //if horizontals rb and hj.edge overlap, flag for joining later ... + if (GetOverlapSegment(IntPoint(hj->edge->xbot, hj->edge->ybot), + IntPoint(hj->edge->xtop, hj->edge->ytop), + IntPoint(rb->xbot, rb->ybot), + IntPoint(rb->xtop, rb->ytop), pt, pt2)) + AddJoin(hj->edge, rb, hj->savedIdx); + } + } + + if( lb->nextInAEL != rb ) + { + if (rb->outIdx >= 0 && rb->prevInAEL->outIdx >= 0 && + SlopesEqual(*rb->prevInAEL, *rb, m_UseFullRange)) + AddJoin(rb, rb->prevInAEL); + + TEdge* e = lb->nextInAEL; + IntPoint pt = IntPoint(lb->xcurr, lb->ycurr); + while( e != rb ) + { + if(!e) throw clipperException("InsertLocalMinimaIntoAEL: missing rightbound!"); + //nb: For calculating winding counts etc, IntersectEdges() assumes + //that param1 will be to the right of param2 ABOVE the intersection ... + IntersectEdges( rb , e , pt , ipNone); //order important here + e = e->nextInAEL; + } + } + PopLocalMinima(); + } +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromAEL(TEdge *e) +{ + TEdge* AelPrev = e->prevInAEL; + TEdge* AelNext = e->nextInAEL; + if( !AelPrev && !AelNext && (e != m_ActiveEdges) ) return; //already deleted + if( AelPrev ) AelPrev->nextInAEL = AelNext; + else m_ActiveEdges = AelNext; + if( AelNext ) AelNext->prevInAEL = AelPrev; + e->nextInAEL = 0; + e->prevInAEL = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromSEL(TEdge *e) +{ + TEdge* SelPrev = e->prevInSEL; + TEdge* SelNext = e->nextInSEL; + if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted + if( SelPrev ) SelPrev->nextInSEL = SelNext; + else m_SortedEdges = SelNext; + if( SelNext ) SelNext->prevInSEL = SelPrev; + e->nextInSEL = 0; + e->prevInSEL = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, + const IntPoint &pt, const IntersectProtects protects) +{ + //e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before + //e2 in AEL except when e1 is being inserted at the intersection point ... + bool e1stops = !(ipLeft & protects) && !e1->nextInLML && + e1->xtop == pt.X && e1->ytop == pt.Y; + bool e2stops = !(ipRight & protects) && !e2->nextInLML && + e2->xtop == pt.X && e2->ytop == pt.Y; + bool e1Contributing = ( e1->outIdx >= 0 ); + bool e2contributing = ( e2->outIdx >= 0 ); + + //update winding counts... + //assumes that e1 will be to the right of e2 ABOVE the intersection + if ( e1->polyType == e2->polyType ) + { + if ( IsEvenOddFillType( *e1) ) + { + int oldE1WindCnt = e1->windCnt; + e1->windCnt = e2->windCnt; + e2->windCnt = oldE1WindCnt; + } else + { + if (e1->windCnt + e2->windDelta == 0 ) e1->windCnt = -e1->windCnt; + else e1->windCnt += e2->windDelta; + if ( e2->windCnt - e1->windDelta == 0 ) e2->windCnt = -e2->windCnt; + else e2->windCnt -= e1->windDelta; + } + } else + { + if (!IsEvenOddFillType(*e2)) e1->windCnt2 += e2->windDelta; + else e1->windCnt2 = ( e1->windCnt2 == 0 ) ? 1 : 0; + if (!IsEvenOddFillType(*e1)) e2->windCnt2 -= e1->windDelta; + else e2->windCnt2 = ( e2->windCnt2 == 0 ) ? 1 : 0; + } + + PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; + if (e1->polyType == ptSubject) + { + e1FillType = m_SubjFillType; + e1FillType2 = m_ClipFillType; + } else + { + e1FillType = m_ClipFillType; + e1FillType2 = m_SubjFillType; + } + if (e2->polyType == ptSubject) + { + e2FillType = m_SubjFillType; + e2FillType2 = m_ClipFillType; + } else + { + e2FillType = m_ClipFillType; + e2FillType2 = m_SubjFillType; + } + + long64 e1Wc, e2Wc; + switch (e1FillType) + { + case pftPositive: e1Wc = e1->windCnt; break; + case pftNegative: e1Wc = -e1->windCnt; break; + default: e1Wc = Abs(e1->windCnt); + } + switch(e2FillType) + { + case pftPositive: e2Wc = e2->windCnt; break; + case pftNegative: e2Wc = -e2->windCnt; break; + default: e2Wc = Abs(e2->windCnt); + } + + if ( e1Contributing && e2contributing ) + { + if ( e1stops || e2stops || + (e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + (e1->polyType != e2->polyType && m_ClipType != ctXor) ) + AddLocalMaxPoly(e1, e2, pt); + else + DoBothEdges( e1, e2, pt ); + } + else if ( e1Contributing ) + { + if ((e2Wc == 0 || e2Wc == 1) && + (m_ClipType != ctIntersection || + e2->polyType == ptSubject || (e2->windCnt2 != 0))) + DoEdge1(e1, e2, pt); + } + else if ( e2contributing ) + { + if ((e1Wc == 0 || e1Wc == 1) && + (m_ClipType != ctIntersection || + e1->polyType == ptSubject || (e1->windCnt2 != 0))) + DoEdge2(e1, e2, pt); + } + else if ( (e1Wc == 0 || e1Wc == 1) && + (e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops ) + { + //neither edge is currently contributing ... + + long64 e1Wc2, e2Wc2; + switch (e1FillType2) + { + case pftPositive: e1Wc2 = e1->windCnt2; break; + case pftNegative : e1Wc2 = -e1->windCnt2; break; + default: e1Wc2 = Abs(e1->windCnt2); + } + switch (e2FillType2) + { + case pftPositive: e2Wc2 = e2->windCnt2; break; + case pftNegative: e2Wc2 = -e2->windCnt2; break; + default: e2Wc2 = Abs(e2->windCnt2); + } + + if (e1->polyType != e2->polyType) + AddLocalMinPoly(e1, e2, pt); + else if (e1Wc == 1 && e2Wc == 1) + switch( m_ClipType ) { + case ctIntersection: + if (e1Wc2 > 0 && e2Wc2 > 0) + AddLocalMinPoly(e1, e2, pt); + break; + case ctUnion: + if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) + AddLocalMinPoly(e1, e2, pt); + break; + case ctDifference: + if (((e1->polyType == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((e1->polyType == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + AddLocalMinPoly(e1, e2, pt); + break; + case ctXor: + AddLocalMinPoly(e1, e2, pt); + } + else + SwapSides( *e1, *e2 ); + } + + if( (e1stops != e2stops) && + ( (e1stops && (e1->outIdx >= 0)) || (e2stops && (e2->outIdx >= 0)) ) ) + { + SwapSides( *e1, *e2 ); + SwapPolyIndexes( *e1, *e2 ); + } + + //finally, delete any non-contributing maxima edges ... + if( e1stops ) DeleteFromAEL( e1 ); + if( e2stops ) DeleteFromAEL( e2 ); +} +//------------------------------------------------------------------------------ + +void Clipper::SetHoleState(TEdge *e, OutRec *outRec) +{ + bool isHole = false; + TEdge *e2 = e->prevInAEL; + while (e2) + { + if (e2->outIdx >= 0) + { + isHole = !isHole; + if (! outRec->FirstLeft) + outRec->FirstLeft = m_PolyOuts[e2->outIdx]; + } + e2 = e2->prevInAEL; + } + if (isHole) outRec->isHole = true; +} +//------------------------------------------------------------------------------ + +OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) +{ + //work out which polygon fragment has the correct hole state ... + OutPt *outPt1 = outRec1->bottomPt; + OutPt *outPt2 = outRec2->bottomPt; + if (outPt1->pt.Y > outPt2->pt.Y) return outRec1; + else if (outPt1->pt.Y < outPt2->pt.Y) return outRec2; + else if (outPt1->pt.X < outPt2->pt.X) return outRec1; + else if (outPt1->pt.X > outPt2->pt.X) return outRec2; + else if (outPt1->next == outPt1) return outRec2; + else if (outPt2->next == outPt2) return outRec1; + else if (FirstIsBottomPt(outPt1, outPt2)) return outRec1; + else return outRec2; +} +//------------------------------------------------------------------------------ + +bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) +{ + do + { + outRec1 = outRec1->FirstLeft; + if (outRec1 == outRec2) return true; + } while (outRec1); + return false; +} +//------------------------------------------------------------------------------ + +void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) +{ + //get the start and ends of both output polygons ... + OutRec *outRec1 = m_PolyOuts[e1->outIdx]; + OutRec *outRec2 = m_PolyOuts[e2->outIdx]; + + OutRec *holeStateRec; + if (Param1RightOfParam2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + OutPt* p1_lft = outRec1->pts; + OutPt* p1_rt = p1_lft->prev; + OutPt* p2_lft = outRec2->pts; + OutPt* p2_rt = p2_lft->prev; + + EdgeSide side; + //join e2 poly onto e1 poly and delete pointers to e2 ... + if( e1->side == esLeft ) + { + if( e2->side == esLeft ) + { + //z y x a b c + ReversePolyPtLinks(p2_lft); + p2_lft->next = p1_lft; + p1_lft->prev = p2_lft; + p1_rt->next = p2_rt; + p2_rt->prev = p1_rt; + outRec1->pts = p2_rt; + } else + { + //x y z a b c + p2_rt->next = p1_lft; + p1_lft->prev = p2_rt; + p2_lft->prev = p1_rt; + p1_rt->next = p2_lft; + outRec1->pts = p2_lft; + } + side = esLeft; + } else + { + if( e2->side == esRight ) + { + //a b c z y x + ReversePolyPtLinks(p2_lft); + p1_rt->next = p2_rt; + p2_rt->prev = p1_rt; + p2_lft->next = p1_lft; + p1_lft->prev = p2_lft; + } else + { + //a b c x y z + p1_rt->next = p2_lft; + p2_lft->prev = p1_rt; + p1_lft->prev = p2_rt; + p2_rt->next = p1_lft; + } + side = esRight; + } + + if (holeStateRec == outRec2) + { + outRec1->bottomPt = outRec2->bottomPt; + outRec1->bottomPt->idx = outRec1->idx; + if (outRec2->FirstLeft != outRec1) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec1->isHole = outRec2->isHole; + } + outRec2->pts = 0; + outRec2->bottomPt = 0; + + outRec2->FirstLeft = outRec1; + + int OKIdx = e1->outIdx; + int ObsoleteIdx = e2->outIdx; + + e1->outIdx = -1; //nb: safe because we only get here via AddLocalMaxPoly + e2->outIdx = -1; + + TEdge* e = m_ActiveEdges; + while( e ) + { + if( e->outIdx == ObsoleteIdx ) + { + e->outIdx = OKIdx; + e->side = side; + break; + } + e = e->nextInAEL; + } + + for (JoinList::size_type i = 0; i < m_Joins.size(); ++i) + { + if (m_Joins[i]->poly1Idx == ObsoleteIdx) m_Joins[i]->poly1Idx = OKIdx; + if (m_Joins[i]->poly2Idx == ObsoleteIdx) m_Joins[i]->poly2Idx = OKIdx; + } + + for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) + { + if (m_HorizJoins[i]->savedIdx == ObsoleteIdx) + m_HorizJoins[i]->savedIdx = OKIdx; + } + +} +//------------------------------------------------------------------------------ + +OutRec* Clipper::CreateOutRec() +{ + OutRec* result = new OutRec; + result->isHole = false; + result->FirstLeft = 0; + result->pts = 0; + result->bottomPt = 0; + result->polyNode = 0; + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::AddOutPt(TEdge *e, const IntPoint &pt) +{ + bool ToFront = (e->side == esLeft); + if( e->outIdx < 0 ) + { + OutRec *outRec = CreateOutRec(); + m_PolyOuts.push_back(outRec); + outRec->idx = (int)m_PolyOuts.size()-1; + e->outIdx = outRec->idx; + OutPt* op = new OutPt; + outRec->pts = op; + outRec->bottomPt = op; + op->pt = pt; + op->idx = outRec->idx; + op->next = op; + op->prev = op; + SetHoleState(e, outRec); + } else + { + OutRec *outRec = m_PolyOuts[e->outIdx]; + OutPt* op = outRec->pts; + if ((ToFront && PointsEqual(pt, op->pt)) || + (!ToFront && PointsEqual(pt, op->prev->pt))) return; + + OutPt* op2 = new OutPt; + op2->pt = pt; + op2->idx = outRec->idx; + if (op2->pt.Y == outRec->bottomPt->pt.Y && + op2->pt.X < outRec->bottomPt->pt.X) + outRec->bottomPt = op2; + op2->next = op; + op2->prev = op->prev; + op2->prev->next = op2; + op->prev = op2; + if (ToFront) outRec->pts = op2; + } +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessHorizontals() +{ + TEdge* horzEdge = m_SortedEdges; + while( horzEdge ) + { + DeleteFromSEL( horzEdge ); + ProcessHorizontal( horzEdge ); + horzEdge = m_SortedEdges; + } +} +//------------------------------------------------------------------------------ + +bool Clipper::IsTopHorz(const long64 XPos) +{ + TEdge* e = m_SortedEdges; + while( e ) + { + if( ( XPos >= std::min(e->xcurr, e->xtop) ) && + ( XPos <= std::max(e->xcurr, e->xtop) ) ) return false; + e = e->nextInSEL; + } + return true; +} +//------------------------------------------------------------------------------ + +bool IsMinima(TEdge *e) +{ + return e && (e->prev->nextInLML != e) && (e->next->nextInLML != e); +} +//------------------------------------------------------------------------------ + +bool IsMaxima(TEdge *e, const long64 Y) +{ + return e && e->ytop == Y && !e->nextInLML; +} +//------------------------------------------------------------------------------ + +bool IsIntermediate(TEdge *e, const long64 Y) +{ + return e->ytop == Y && e->nextInLML; +} +//------------------------------------------------------------------------------ + +TEdge *GetMaximaPair(TEdge *e) +{ + if( !IsMaxima(e->next, e->ytop) || e->next->xtop != e->xtop ) + return e->prev; else + return e->next; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInAEL(TEdge *edge1, TEdge *edge2) +{ + if( edge1->nextInAEL == edge2 ) + { + TEdge* next = edge2->nextInAEL; + if( next ) next->prevInAEL = edge1; + TEdge* prev = edge1->prevInAEL; + if( prev ) prev->nextInAEL = edge2; + edge2->prevInAEL = prev; + edge2->nextInAEL = edge1; + edge1->prevInAEL = edge2; + edge1->nextInAEL = next; + } + else if( edge2->nextInAEL == edge1 ) + { + TEdge* next = edge1->nextInAEL; + if( next ) next->prevInAEL = edge2; + TEdge* prev = edge2->prevInAEL; + if( prev ) prev->nextInAEL = edge1; + edge1->prevInAEL = prev; + edge1->nextInAEL = edge2; + edge2->prevInAEL = edge1; + edge2->nextInAEL = next; + } + else + { + TEdge* next = edge1->nextInAEL; + TEdge* prev = edge1->prevInAEL; + edge1->nextInAEL = edge2->nextInAEL; + if( edge1->nextInAEL ) edge1->nextInAEL->prevInAEL = edge1; + edge1->prevInAEL = edge2->prevInAEL; + if( edge1->prevInAEL ) edge1->prevInAEL->nextInAEL = edge1; + edge2->nextInAEL = next; + if( edge2->nextInAEL ) edge2->nextInAEL->prevInAEL = edge2; + edge2->prevInAEL = prev; + if( edge2->prevInAEL ) edge2->prevInAEL->nextInAEL = edge2; + } + + if( !edge1->prevInAEL ) m_ActiveEdges = edge1; + else if( !edge2->prevInAEL ) m_ActiveEdges = edge2; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInSEL(TEdge *edge1, TEdge *edge2) +{ + if( !( edge1->nextInSEL ) && !( edge1->prevInSEL ) ) return; + if( !( edge2->nextInSEL ) && !( edge2->prevInSEL ) ) return; + + if( edge1->nextInSEL == edge2 ) + { + TEdge* next = edge2->nextInSEL; + if( next ) next->prevInSEL = edge1; + TEdge* prev = edge1->prevInSEL; + if( prev ) prev->nextInSEL = edge2; + edge2->prevInSEL = prev; + edge2->nextInSEL = edge1; + edge1->prevInSEL = edge2; + edge1->nextInSEL = next; + } + else if( edge2->nextInSEL == edge1 ) + { + TEdge* next = edge1->nextInSEL; + if( next ) next->prevInSEL = edge2; + TEdge* prev = edge2->prevInSEL; + if( prev ) prev->nextInSEL = edge1; + edge1->prevInSEL = prev; + edge1->nextInSEL = edge2; + edge2->prevInSEL = edge1; + edge2->nextInSEL = next; + } + else + { + TEdge* next = edge1->nextInSEL; + TEdge* prev = edge1->prevInSEL; + edge1->nextInSEL = edge2->nextInSEL; + if( edge1->nextInSEL ) edge1->nextInSEL->prevInSEL = edge1; + edge1->prevInSEL = edge2->prevInSEL; + if( edge1->prevInSEL ) edge1->prevInSEL->nextInSEL = edge1; + edge2->nextInSEL = next; + if( edge2->nextInSEL ) edge2->nextInSEL->prevInSEL = edge2; + edge2->prevInSEL = prev; + if( edge2->prevInSEL ) edge2->prevInSEL->nextInSEL = edge2; + } + + if( !edge1->prevInSEL ) m_SortedEdges = edge1; + else if( !edge2->prevInSEL ) m_SortedEdges = edge2; +} +//------------------------------------------------------------------------------ + +TEdge* GetNextInAEL(TEdge *e, Direction dir) +{ + return dir == dLeftToRight ? e->nextInAEL : e->prevInAEL; +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessHorizontal(TEdge *horzEdge) +{ + Direction dir; + long64 horzLeft, horzRight; + + if( horzEdge->xcurr < horzEdge->xtop ) + { + horzLeft = horzEdge->xcurr; + horzRight = horzEdge->xtop; + dir = dLeftToRight; + } else + { + horzLeft = horzEdge->xtop; + horzRight = horzEdge->xcurr; + dir = dRightToLeft; + } + + TEdge* eMaxPair; + if( horzEdge->nextInLML ) eMaxPair = 0; + else eMaxPair = GetMaximaPair(horzEdge); + + TEdge* e = GetNextInAEL( horzEdge , dir ); + while( e ) + { + TEdge* eNext = GetNextInAEL( e, dir ); + + if (eMaxPair || + ((dir == dLeftToRight) && (e->xcurr <= horzRight)) || + ((dir == dRightToLeft) && (e->xcurr >= horzLeft))) + { + //ok, so far it looks like we're still in range of the horizontal edge + if ( e->xcurr == horzEdge->xtop && !eMaxPair ) + { + if (SlopesEqual(*e, *horzEdge->nextInLML, m_UseFullRange)) + { + //if output polygons share an edge, they'll need joining later ... + if (horzEdge->outIdx >= 0 && e->outIdx >= 0) + AddJoin(horzEdge->nextInLML, e, horzEdge->outIdx); + break; //we've reached the end of the horizontal line + } + else if (e->dx < horzEdge->nextInLML->dx) + //we really have got to the end of the intermediate horz edge so quit. + //nb: More -ve slopes follow more +ve slopes ABOVE the horizontal. + break; + } + + if( e == eMaxPair ) + { + //horzEdge is evidently a maxima horizontal and we've arrived at its end. + if (dir == dLeftToRight) + IntersectEdges(horzEdge, e, IntPoint(e->xcurr, horzEdge->ycurr), ipNone); + else + IntersectEdges(e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), ipNone); + if (eMaxPair->outIdx >= 0) throw clipperException("ProcessHorizontal error"); + return; + } + else if( NEAR_EQUAL(e->dx, HORIZONTAL) && !IsMinima(e) && !(e->xcurr > e->xtop) ) + { + //An overlapping horizontal edge. Overlapping horizontal edges are + //processed as if layered with the current horizontal edge (horizEdge) + //being infinitesimally lower that the next (e). Therfore, we + //intersect with e only if e.xcurr is within the bounds of horzEdge ... + if( dir == dLeftToRight ) + IntersectEdges( horzEdge , e, IntPoint(e->xcurr, horzEdge->ycurr), + (IsTopHorz( e->xcurr ))? ipLeft : ipBoth ); + else + IntersectEdges( e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), + (IsTopHorz( e->xcurr ))? ipRight : ipBoth ); + } + else if( dir == dLeftToRight ) + { + IntersectEdges( horzEdge, e, IntPoint(e->xcurr, horzEdge->ycurr), + (IsTopHorz( e->xcurr ))? ipLeft : ipBoth ); + } + else + { + IntersectEdges( e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), + (IsTopHorz( e->xcurr ))? ipRight : ipBoth ); + } + SwapPositionsInAEL( horzEdge, e ); + } + else if( (dir == dLeftToRight && e->xcurr > horzRight && m_SortedEdges) || + (dir == dRightToLeft && e->xcurr < horzLeft && m_SortedEdges) ) break; + e = eNext; + } //end while + + if( horzEdge->nextInLML ) + { + if( horzEdge->outIdx >= 0 ) + AddOutPt( horzEdge, IntPoint(horzEdge->xtop, horzEdge->ytop)); + UpdateEdgeIntoAEL( horzEdge ); + } + else + { + if ( horzEdge->outIdx >= 0 ) + IntersectEdges( horzEdge, eMaxPair, + IntPoint(horzEdge->xtop, horzEdge->ycurr), ipBoth); + if (eMaxPair->outIdx >= 0) throw clipperException("ProcessHorizontal error"); + DeleteFromAEL(eMaxPair); + DeleteFromAEL(horzEdge); + } +} +//------------------------------------------------------------------------------ + +void Clipper::UpdateEdgeIntoAEL(TEdge *&e) +{ + if( !e->nextInLML ) throw + clipperException("UpdateEdgeIntoAEL: invalid call"); + TEdge* AelPrev = e->prevInAEL; + TEdge* AelNext = e->nextInAEL; + e->nextInLML->outIdx = e->outIdx; + if( AelPrev ) AelPrev->nextInAEL = e->nextInLML; + else m_ActiveEdges = e->nextInLML; + if( AelNext ) AelNext->prevInAEL = e->nextInLML; + e->nextInLML->side = e->side; + e->nextInLML->windDelta = e->windDelta; + e->nextInLML->windCnt = e->windCnt; + e->nextInLML->windCnt2 = e->windCnt2; + e = e->nextInLML; + e->prevInAEL = AelPrev; + e->nextInAEL = AelNext; + if( !NEAR_EQUAL(e->dx, HORIZONTAL) ) InsertScanbeam( e->ytop ); +} +//------------------------------------------------------------------------------ + +bool Clipper::ProcessIntersections(const long64 botY, const long64 topY) +{ + if( !m_ActiveEdges ) return true; + try { + BuildIntersectList(botY, topY); + if ( !m_IntersectNodes) return true; + if ( FixupIntersections() ) ProcessIntersectList(); + else return false; + } + catch(...) { + m_SortedEdges = 0; + DisposeIntersectNodes(); + throw clipperException("ProcessIntersections error"); + } + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeIntersectNodes() +{ + while ( m_IntersectNodes ) + { + IntersectNode* iNode = m_IntersectNodes->next; + delete m_IntersectNodes; + m_IntersectNodes = iNode; + } +} +//------------------------------------------------------------------------------ + +void Clipper::BuildIntersectList(const long64 botY, const long64 topY) +{ + if ( !m_ActiveEdges ) return; + + //prepare for sorting ... + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while( e ) + { + e->prevInSEL = e->prevInAEL; + e->nextInSEL = e->nextInAEL; + e->tmpX = TopX( *e, topY ); + e = e->nextInAEL; + } + + //bubblesort ... + bool isModified = true; + while( isModified && m_SortedEdges ) + { + isModified = false; + e = m_SortedEdges; + while( e->nextInSEL ) + { + TEdge *eNext = e->nextInSEL; + IntPoint pt; + if(e->tmpX > eNext->tmpX && + IntersectPoint(*e, *eNext, pt, m_UseFullRange)) + { + if (pt.Y > botY) + { + pt.Y = botY; + pt.X = TopX(*e, pt.Y); + } + AddIntersectNode( e, eNext, pt ); + SwapPositionsInSEL(e, eNext); + isModified = true; + } + else + e = eNext; + } + if( e->prevInSEL ) e->prevInSEL->nextInSEL = 0; + else break; + } + m_SortedEdges = 0; +} +//------------------------------------------------------------------------------ + +bool ProcessParam1BeforeParam2(const IntersectNode &node1, const IntersectNode &node2) +{ + bool result; + if (node1.pt.Y == node2.pt.Y) + { + if (node1.edge1 == node2.edge1 || node1.edge2 == node2.edge1) + { + result = node2.pt.X > node1.pt.X; + return node2.edge1->dx > 0 ? !result : result; + } + else if (node1.edge1 == node2.edge2 || node1.edge2 == node2.edge2) + { + result = node2.pt.X > node1.pt.X; + return node2.edge2->dx > 0 ? !result : result; + } + else return node2.pt.X > node1.pt.X; + } + else return node1.pt.Y > node2.pt.Y; +} +//------------------------------------------------------------------------------ + +void Clipper::AddIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt) +{ + IntersectNode* newNode = new IntersectNode; + newNode->edge1 = e1; + newNode->edge2 = e2; + newNode->pt = pt; + newNode->next = 0; + if( !m_IntersectNodes ) m_IntersectNodes = newNode; + else if( ProcessParam1BeforeParam2(*newNode, *m_IntersectNodes) ) + { + newNode->next = m_IntersectNodes; + m_IntersectNodes = newNode; + } + else + { + IntersectNode* iNode = m_IntersectNodes; + while( iNode->next && ProcessParam1BeforeParam2(*iNode->next, *newNode) ) + iNode = iNode->next; + newNode->next = iNode->next; + iNode->next = newNode; + } +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessIntersectList() +{ + while( m_IntersectNodes ) + { + IntersectNode* iNode = m_IntersectNodes->next; + { + IntersectEdges( m_IntersectNodes->edge1 , + m_IntersectNodes->edge2 , m_IntersectNodes->pt, ipBoth ); + SwapPositionsInAEL( m_IntersectNodes->edge1 , m_IntersectNodes->edge2 ); + } + delete m_IntersectNodes; + m_IntersectNodes = iNode; + } +} +//------------------------------------------------------------------------------ + +void Clipper::DoMaxima(TEdge *e, long64 topY) +{ + TEdge* eMaxPair = GetMaximaPair(e); + long64 X = e->xtop; + TEdge* eNext = e->nextInAEL; + while( eNext != eMaxPair ) + { + if (!eNext) throw clipperException("DoMaxima error"); + IntersectEdges( e, eNext, IntPoint(X, topY), ipBoth ); + SwapPositionsInAEL(e, eNext); + eNext = eNext->nextInAEL; + } + if( e->outIdx < 0 && eMaxPair->outIdx < 0 ) + { + DeleteFromAEL( e ); + DeleteFromAEL( eMaxPair ); + } + else if( e->outIdx >= 0 && eMaxPair->outIdx >= 0 ) + { + IntersectEdges( e, eMaxPair, IntPoint(X, topY), ipNone ); + } + else throw clipperException("DoMaxima error"); +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessEdgesAtTopOfScanbeam(const long64 topY) +{ + TEdge* e = m_ActiveEdges; + while( e ) + { + //1. process maxima, treating them as if they're 'bent' horizontal edges, + // but exclude maxima with horizontal edges. nb: e can't be a horizontal. + if( IsMaxima(e, topY) && !NEAR_EQUAL(GetMaximaPair(e)->dx, HORIZONTAL) ) + { + //'e' might be removed from AEL, as may any following edges so ... + TEdge* ePrev = e->prevInAEL; + DoMaxima(e, topY); + if( !ePrev ) e = m_ActiveEdges; + else e = ePrev->nextInAEL; + } + else + { + //2. promote horizontal edges, otherwise update xcurr and ycurr ... + if( IsIntermediate(e, topY) && NEAR_EQUAL(e->nextInLML->dx, HORIZONTAL) ) + { + if (e->outIdx >= 0) + { + AddOutPt(e, IntPoint(e->xtop, e->ytop)); + + for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) + { + IntPoint pt, pt2; + HorzJoinRec* hj = m_HorizJoins[i]; + if (GetOverlapSegment(IntPoint(hj->edge->xbot, hj->edge->ybot), + IntPoint(hj->edge->xtop, hj->edge->ytop), + IntPoint(e->nextInLML->xbot, e->nextInLML->ybot), + IntPoint(e->nextInLML->xtop, e->nextInLML->ytop), pt, pt2)) + AddJoin(hj->edge, e->nextInLML, hj->savedIdx, e->outIdx); + } + + AddHorzJoin(e->nextInLML, e->outIdx); + } + UpdateEdgeIntoAEL(e); + AddEdgeToSEL(e); + } else + { + //this just simplifies horizontal processing ... + e->xcurr = TopX( *e, topY ); + e->ycurr = topY; + } + e = e->nextInAEL; + } + } + + //3. Process horizontals at the top of the scanbeam ... + ProcessHorizontals(); + + //4. Promote intermediate vertices ... + e = m_ActiveEdges; + while( e ) + { + if( IsIntermediate( e, topY ) ) + { + if( e->outIdx >= 0 ) AddOutPt(e, IntPoint(e->xtop,e->ytop)); + UpdateEdgeIntoAEL(e); + + //if output polygons share an edge, they'll need joining later ... + TEdge* ePrev = e->prevInAEL; + TEdge* eNext = e->nextInAEL; + if (ePrev && ePrev->xcurr == e->xbot && + ePrev->ycurr == e->ybot && e->outIdx >= 0 && + ePrev->outIdx >= 0 && ePrev->ycurr > ePrev->ytop && + SlopesEqual(*e, *ePrev, m_UseFullRange)) + { + AddOutPt(ePrev, IntPoint(e->xbot, e->ybot)); + AddJoin(e, ePrev); + } + else if (eNext && eNext->xcurr == e->xbot && + eNext->ycurr == e->ybot && e->outIdx >= 0 && + eNext->outIdx >= 0 && eNext->ycurr > eNext->ytop && + SlopesEqual(*e, *eNext, m_UseFullRange)) + { + AddOutPt(eNext, IntPoint(e->xbot, e->ybot)); + AddJoin(e, eNext); + } + } + e = e->nextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::FixupOutPolygon(OutRec &outRec) +{ + //FixupOutPolygon() - removes duplicate points and simplifies consecutive + //parallel edges by removing the middle vertex. + OutPt *lastOK = 0; + outRec.pts = outRec.bottomPt; + OutPt *pp = outRec.bottomPt; + + for (;;) + { + if (pp->prev == pp || pp->prev == pp->next ) + { + DisposeOutPts(pp); + outRec.pts = 0; + outRec.bottomPt = 0; + return; + } + //test for duplicate points and for same slope (cross-product) ... + if ( PointsEqual(pp->pt, pp->next->pt) || + SlopesEqual(pp->prev->pt, pp->pt, pp->next->pt, m_UseFullRange) ) + { + lastOK = 0; + OutPt *tmp = pp; + if (pp == outRec.bottomPt) + outRec.bottomPt = 0; //flags need for updating + pp->prev->next = pp->next; + pp->next->prev = pp->prev; + pp = pp->prev; + delete tmp; + } + else if (pp == lastOK) break; + else + { + if (!lastOK) lastOK = pp; + pp = pp->next; + } + } + if (!outRec.bottomPt) { + outRec.bottomPt = GetBottomPt(pp); + outRec.bottomPt->idx = outRec.idx; + outRec.pts = outRec.bottomPt; + } +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult(Polygons &polys) +{ + int k = 0; + polys.resize(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + if (m_PolyOuts[i]->pts) + { + Polygon* pg = &polys[k]; + pg->clear(); + OutPt* p = m_PolyOuts[i]->pts; + do + { + pg->push_back(p->pt); + p = p->prev; + } while (p != m_PolyOuts[i]->pts); + //make sure each polygon has at least 3 vertices ... + if (pg->size() < 3) pg->clear(); else k++; + } + } + polys.resize(k); +} +//------------------------------------------------------------------------------ + +int PointCount(OutPt *pts) +{ + if (!pts) return 0; + int result = 0; + OutPt* p = pts; + do + { + result++; + p = p->next; + } + while (p != pts); + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult2(PolyTree& polytree) +{ + polytree.Clear(); + polytree.AllNodes.reserve(m_PolyOuts.size()); + //add each output polygon/contour to polytree ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec->pts); + if (cnt < 3) continue; + FixHoleLinkage(*outRec); + PolyNode* pn = new PolyNode(); + //nb: polytree takes ownership of all the PolyNodes + polytree.AllNodes.push_back(pn); + outRec->polyNode = pn; + pn->Parent = 0; + pn->Index = 0; + pn->Contour.reserve(cnt); + OutPt *op = outRec->pts; + for (int j = 0; j < cnt; j++) + { + pn->Contour.push_back(op->pt); + op = op->prev; + } + } + + //fixup PolyNode links etc ... + polytree.Childs.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + if (!outRec->polyNode) continue; + if (outRec->FirstLeft) + outRec->FirstLeft->polyNode->AddChild(*outRec->polyNode); + else + polytree.AddChild(*outRec->polyNode); + } +} +//------------------------------------------------------------------------------ + +void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) +{ + TEdge *e1 = int1.edge1; + TEdge *e2 = int1.edge2; + IntPoint p = int1.pt; + + int1.edge1 = int2.edge1; + int1.edge2 = int2.edge2; + int1.pt = int2.pt; + + int2.edge1 = e1; + int2.edge2 = e2; + int2.pt = p; +} +//------------------------------------------------------------------------------ + +bool Clipper::FixupIntersections() +{ + if ( !m_IntersectNodes->next ) return true; + + CopyAELToSEL(); + IntersectNode *int1 = m_IntersectNodes; + IntersectNode *int2 = m_IntersectNodes->next; + while (int2) + { + TEdge *e1 = int1->edge1; + TEdge *e2; + if (e1->prevInSEL == int1->edge2) e2 = e1->prevInSEL; + else if (e1->nextInSEL == int1->edge2) e2 = e1->nextInSEL; + else + { + //The current intersection is out of order, so try and swap it with + //a subsequent intersection ... + while (int2) + { + if (int2->edge1->nextInSEL == int2->edge2 || + int2->edge1->prevInSEL == int2->edge2) break; + else int2 = int2->next; + } + if ( !int2 ) return false; //oops!!! + + //found an intersect node that can be swapped ... + SwapIntersectNodes(*int1, *int2); + e1 = int1->edge1; + e2 = int1->edge2; + } + SwapPositionsInSEL(e1, e2); + int1 = int1->next; + int2 = int1->next; + } + + m_SortedEdges = 0; + + //finally, check the last intersection too ... + return (int1->edge1->prevInSEL == int1->edge2 || + int1->edge1->nextInSEL == int1->edge2); +} +//------------------------------------------------------------------------------ + +bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) +{ + return e2.xcurr == e1.xcurr ? e2.dx > e1.dx : e2.xcurr < e1.xcurr; +} +//------------------------------------------------------------------------------ + +void Clipper::InsertEdgeIntoAEL(TEdge *edge) +{ + edge->prevInAEL = 0; + edge->nextInAEL = 0; + if( !m_ActiveEdges ) + { + m_ActiveEdges = edge; + } + else if( E2InsertsBeforeE1(*m_ActiveEdges, *edge) ) + { + edge->nextInAEL = m_ActiveEdges; + m_ActiveEdges->prevInAEL = edge; + m_ActiveEdges = edge; + } else + { + TEdge* e = m_ActiveEdges; + while( e->nextInAEL && !E2InsertsBeforeE1(*e->nextInAEL , *edge) ) + e = e->nextInAEL; + edge->nextInAEL = e->nextInAEL; + if( e->nextInAEL ) e->nextInAEL->prevInAEL = edge; + edge->prevInAEL = e; + e->nextInAEL = edge; + } +} +//---------------------------------------------------------------------- + +void Clipper::DoEdge1(TEdge *edge1, TEdge *edge2, const IntPoint &pt) +{ + AddOutPt(edge1, pt); + SwapSides(*edge1, *edge2); + SwapPolyIndexes(*edge1, *edge2); +} +//---------------------------------------------------------------------- + +void Clipper::DoEdge2(TEdge *edge1, TEdge *edge2, const IntPoint &pt) +{ + AddOutPt(edge2, pt); + SwapSides(*edge1, *edge2); + SwapPolyIndexes(*edge1, *edge2); +} +//---------------------------------------------------------------------- + +void Clipper::DoBothEdges(TEdge *edge1, TEdge *edge2, const IntPoint &pt) +{ + AddOutPt(edge1, pt); + AddOutPt(edge2, pt); + SwapSides( *edge1 , *edge2 ); + SwapPolyIndexes( *edge1 , *edge2 ); +} +//---------------------------------------------------------------------- + +bool Clipper::JoinPoints(const JoinRec *j, OutPt *&p1, OutPt *&p2) +{ + OutRec *outRec1 = m_PolyOuts[j->poly1Idx]; + OutRec *outRec2 = m_PolyOuts[j->poly2Idx]; + if (!outRec1 || !outRec2) return false; + OutPt *pp1a = outRec1->pts; + OutPt *pp2a = outRec2->pts; + IntPoint pt1 = j->pt2a, pt2 = j->pt2b; + IntPoint pt3 = j->pt1a, pt4 = j->pt1b; + if (!FindSegment(pp1a, pt1, pt2)) return false; + if (outRec1 == outRec2) + { + //we're searching the same polygon for overlapping segments so + //segment 2 mustn't be the same as segment 1 ... + pp2a = pp1a->next; + if (!FindSegment(pp2a, pt3, pt4) || (pp2a == pp1a)) return false; + } + else if (!FindSegment(pp2a, pt3, pt4)) return false; + + if (!GetOverlapSegment(pt1, pt2, pt3, pt4, pt1, pt2)) return false; + + OutPt *p3, *p4, *prev = pp1a->prev; + //get p1 & p2 polypts - the overlap start & endpoints on poly1 + if (PointsEqual(pp1a->pt, pt1)) p1 = pp1a; + else if (PointsEqual(prev->pt, pt1)) p1 = prev; + else p1 = InsertPolyPtBetween(pp1a, prev, pt1); + + if (PointsEqual(pp1a->pt, pt2)) p2 = pp1a; + else if (PointsEqual(prev->pt, pt2)) p2 = prev; + else if ((p1 == pp1a) || (p1 == prev)) + p2 = InsertPolyPtBetween(pp1a, prev, pt2); + else if (Pt3IsBetweenPt1AndPt2(pp1a->pt, p1->pt, pt2)) + p2 = InsertPolyPtBetween(pp1a, p1, pt2); else + p2 = InsertPolyPtBetween(p1, prev, pt2); + + //get p3 & p4 polypts - the overlap start & endpoints on poly2 + prev = pp2a->prev; + if (PointsEqual(pp2a->pt, pt1)) p3 = pp2a; + else if (PointsEqual(prev->pt, pt1)) p3 = prev; + else p3 = InsertPolyPtBetween(pp2a, prev, pt1); + + if (PointsEqual(pp2a->pt, pt2)) p4 = pp2a; + else if (PointsEqual(prev->pt, pt2)) p4 = prev; + else if ((p3 == pp2a) || (p3 == prev)) + p4 = InsertPolyPtBetween(pp2a, prev, pt2); + else if (Pt3IsBetweenPt1AndPt2(pp2a->pt, p3->pt, pt2)) + p4 = InsertPolyPtBetween(pp2a, p3, pt2); else + p4 = InsertPolyPtBetween(p3, prev, pt2); + + //p1.pt == p3.pt and p2.pt == p4.pt so join p1 to p3 and p2 to p4 ... + if (p1->next == p2 && p3->prev == p4) + { + p1->next = p3; + p3->prev = p1; + p2->prev = p4; + p4->next = p2; + return true; + } + else if (p1->prev == p2 && p3->next == p4) + { + p1->prev = p3; + p3->next = p1; + p2->next = p4; + p4->prev = p2; + return true; + } + else + return false; //an orientation is probably wrong +} +//---------------------------------------------------------------------- + +void Clipper::FixupJoinRecs(JoinRec *j, OutPt *pt, unsigned startIdx) +{ + for (JoinList::size_type k = startIdx; k < m_Joins.size(); k++) + { + JoinRec* j2 = m_Joins[k]; + if (j2->poly1Idx == j->poly1Idx && PointIsVertex(j2->pt1a, pt)) + j2->poly1Idx = j->poly2Idx; + if (j2->poly2Idx == j->poly1Idx && PointIsVertex(j2->pt2a, pt)) + j2->poly2Idx = j->poly2Idx; + } +} +//---------------------------------------------------------------------- + +bool Poly2ContainsPoly1(OutPt* outPt1, OutPt* outPt2, bool UseFullInt64Range) +{ + //find the first pt in outPt1 that isn't also a vertex of outPt2 ... + OutPt* outPt = outPt1; + do + { + if (!PointIsVertex(outPt->pt, outPt2)) break; + outPt = outPt->next; + } + while (outPt != outPt1); + bool result; + //sometimes a point on one polygon can be touching the other polygon + //so to be totally confident outPt1 is inside outPt2 repeat ... + do + { + result = PointInPolygon(outPt->pt, outPt2, UseFullInt64Range); + outPt = outPt->next; + } + while (result && outPt != outPt1); + return result; +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) +{ + + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + if (outRec->pts && outRec->FirstLeft == OldOutRec) + { + if (Poly2ContainsPoly1(outRec->pts, NewOutRec->pts, m_UseFullRange)) + outRec->FirstLeft = NewOutRec; + } + } +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) +{ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec; + } +} +//---------------------------------------------------------------------- + +void Clipper::JoinCommonEdges() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + { + JoinRec* j = m_Joins[i]; + + OutRec *outRec1 = m_PolyOuts[j->poly1Idx]; + OutRec *outRec2 = m_PolyOuts[j->poly2Idx]; + + if (!outRec1->pts || !outRec2->pts) continue; + + //get the polygon fragment with the correct hole state (FirstLeft) + //before calling JoinPoints() ... + OutRec *holeStateRec; + if (outRec1 == outRec2) holeStateRec = outRec1; + else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; + else holeStateRec = GetLowermostRec(outRec1, outRec2); + + OutPt *p1, *p2; + if (!JoinPoints(j, p1, p2)) continue; + + if (outRec1 == outRec2) + { + //instead of joining two polygons, we've just created a new one by + //splitting one polygon into two. + outRec1->pts = GetBottomPt(p1); + outRec1->bottomPt = outRec1->pts; + outRec1->bottomPt->idx = outRec1->idx; + outRec2 = CreateOutRec(); + m_PolyOuts.push_back(outRec2); + outRec2->idx = (int)m_PolyOuts.size()-1; + j->poly2Idx = outRec2->idx; + outRec2->pts = GetBottomPt(p2); + outRec2->bottomPt = outRec2->pts; + outRec2->bottomPt->idx = outRec2->idx; + + if (Poly2ContainsPoly1(outRec2->pts, outRec1->pts, m_UseFullRange)) + { + //outRec2 is contained by outRec1 ... + outRec2->isHole = !outRec1->isHole; + outRec2->FirstLeft = outRec1; + + FixupJoinRecs(j, p2, i+1); + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + + FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() + + + if ((outRec2->isHole ^ m_ReverseOutput) == (Area(*outRec2, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec2->pts); + + } else if (Poly2ContainsPoly1(outRec1->pts, outRec2->pts, m_UseFullRange)) + { + //outRec1 is contained by outRec2 ... + outRec2->isHole = outRec1->isHole; + outRec1->isHole = !outRec2->isHole; + outRec2->FirstLeft = outRec1->FirstLeft; + outRec1->FirstLeft = outRec2; + + FixupJoinRecs(j, p2, i+1); + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); + + FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() + + if ((outRec1->isHole ^ m_ReverseOutput) == (Area(*outRec1, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec1->pts); + } + else + { + //the 2 polygons are completely separate ... + outRec2->isHole = outRec1->isHole; + outRec2->FirstLeft = outRec1->FirstLeft; + + FixupJoinRecs(j, p2, i+1); + + //fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); + + FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() + } + + } else + { + //joined 2 polygons together ... + + //cleanup redundant edges ... + FixupOutPolygon(*outRec1); + + //delete the obsolete pointer ... + int OKIdx = outRec1->idx; + int ObsoleteIdx = outRec2->idx; + outRec2->pts = 0; + outRec2->bottomPt = 0; + + outRec1->isHole = holeStateRec->isHole; + if (holeStateRec == outRec2) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec2->FirstLeft = outRec1; + + //now fixup any subsequent Joins that match this polygon + for (JoinList::size_type k = i+1; k < m_Joins.size(); k++) + { + JoinRec* j2 = m_Joins[k]; + if (j2->poly1Idx == ObsoleteIdx) j2->poly1Idx = OKIdx; + if (j2->poly2Idx == ObsoleteIdx) j2->poly2Idx = OKIdx; + } + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + } + } +} +//------------------------------------------------------------------------------ + +void ReversePolygon(Polygon& p) +{ + std::reverse(p.begin(), p.end()); +} +//------------------------------------------------------------------------------ + +void ReversePolygons(Polygons& p) +{ + for (Polygons::size_type i = 0; i < p.size(); ++i) + ReversePolygon(p[i]); +} + +//------------------------------------------------------------------------------ +// OffsetPolygon functions ... +//------------------------------------------------------------------------------ + +struct DoublePoint +{ + double X; + double Y; + DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} +}; +//------------------------------------------------------------------------------ + +Polygon BuildArc(const IntPoint &pt, + const double a1, const double a2, const double r) +{ + long64 steps = std::max(6, int(std::sqrt(std::fabs(r)) * std::fabs(a2 - a1))); + if (steps > 0x100) steps = 0x100; + int n = (unsigned)steps; + Polygon result(n); + double da = (a2 - a1) / (n -1); + double a = a1; + for (int i = 0; i < n; ++i) + { + result[i].X = pt.X + Round(std::cos(a)*r); + result[i].Y = pt.Y + Round(std::sin(a)*r); + a += da; + } + return result; +} +//------------------------------------------------------------------------------ + +DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) +{ + if(pt2.X == pt1.X && pt2.Y == pt1.Y) + return DoublePoint(0, 0); + + double dx = (double)(pt2.X - pt1.X); + double dy = (double)(pt2.Y - pt1.Y); + double f = 1 *1.0/ std::sqrt( dx*dx + dy*dy ); + dx *= f; + dy *= f; + return DoublePoint(dy, -dx); +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +class PolyOffsetBuilder +{ +private: + Polygons m_p; + Polygon* m_curr_poly; + std::vector normals; + double m_delta, m_RMin, m_R; + size_t m_i, m_j, m_k; + static const int buffLength = 128; + JoinType m_jointype; + +public: + +PolyOffsetBuilder(const Polygons& in_polys, Polygons& out_polys, + double delta, JoinType jointype, double MiterLimit, bool AutoFix) +{ + //nb precondition - out_polys != ptsin_polys + if (NEAR_ZERO(delta)) + { + out_polys = in_polys; + return; + } + + this->m_p = in_polys; + this->m_delta = delta; + this->m_jointype = jointype; + + //ChecksInput - fixes polygon orientation if necessary and removes + //duplicate vertices. Can be set false when you're sure that polygon + //orientation is correct and that there are no duplicate vertices. + if (AutoFix) + { + size_t Len = m_p.size(), botI = 0; + while (botI < Len && m_p[botI].size() == 0) botI++; + if (botI == Len) return; + + //botPt: used to find the lowermost (in inverted Y-axis) & leftmost point + //This point (on m_p[botI]) must be on an outer polygon ring and if + //its orientation is false (counterclockwise) then assume all polygons + //need reversing ... + IntPoint botPt = m_p[botI][0]; + for (size_t i = botI; i < Len; ++i) + { + if (m_p[i].size() < 3) continue; + if (UpdateBotPt(m_p[i][0], botPt)) botI = i; + Polygon::iterator it = m_p[i].begin() +1; + while (it != m_p[i].end()) + { + if (PointsEqual(*it, *(it -1))) + it = m_p[i].erase(it); + else + { + if (UpdateBotPt(*it, botPt)) botI = i; + ++it; + } + } + } + if (!Orientation(m_p[botI])) + ReversePolygons(m_p); + } + + if (MiterLimit <= 1) MiterLimit = 1; + m_RMin = 2.0/(MiterLimit*MiterLimit); + + double deltaSq = delta*delta; + out_polys.clear(); + out_polys.resize(m_p.size()); + for (m_i = 0; m_i < m_p.size(); m_i++) + { + m_curr_poly = &out_polys[m_i]; + size_t len = m_p[m_i].size(); + if (len > 1 && m_p[m_i][0].X == m_p[m_i][len - 1].X && + m_p[m_i][0].Y == m_p[m_i][len-1].Y) len--; + + //when 'shrinking' polygons - to minimize artefacts + //strip those polygons that have an area < pi * delta^2 ... + double a1 = Area(m_p[m_i]); + if (delta < 0) { if (a1 > 0 && a1 < deltaSq *pi) len = 0; } + else if (a1 < 0 && -a1 < deltaSq *pi) len = 0; //holes have neg. area + + if (len == 0 || (len < 3 && delta <= 0)) + continue; + else if (len == 1) + { + Polygon arc; + arc = BuildArc(m_p[m_i][len-1], 0, 2 * pi, delta); + out_polys[m_i] = arc; + continue; + } + + //build normals ... + normals.clear(); + normals.resize(len); + normals[len-1] = GetUnitNormal(m_p[m_i][len-1], m_p[m_i][0]); + for (m_j = 0; m_j < len -1; ++m_j) + normals[m_j] = GetUnitNormal(m_p[m_i][m_j], m_p[m_i][m_j+1]); + + m_k = len -1; + for (m_j = 0; m_j < len; ++m_j) + { + switch (jointype) + { + case jtMiter: + { + m_R = 1 + (normals[m_j].X*normals[m_k].X + + normals[m_j].Y*normals[m_k].Y); + if (m_R >= m_RMin) DoMiter(); else DoSquare(MiterLimit); + break; + } + case jtSquare: DoSquare(); break; + case jtRound: DoRound(); break; + } + m_k = m_j; + } + } + + //finally, clean up untidy corners using Clipper ... + Clipper clpr; + clpr.AddPolygons(out_polys, ptSubject); + if (delta > 0) + { + if (!clpr.Execute(ctUnion, out_polys, pftPositive, pftPositive)) + out_polys.clear(); + } + else + { + IntRect r = clpr.GetBounds(); + Polygon outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPolygon(outer, ptSubject); + if (clpr.Execute(ctUnion, out_polys, pftNegative, pftNegative)) + { + out_polys.erase(out_polys.begin()); + ReversePolygons(out_polys); + + } else + out_polys.clear(); + } +} +//------------------------------------------------------------------------------ + +private: + +void AddPoint(const IntPoint& pt) +{ + Polygon::size_type len = m_curr_poly->size(); + if (len == m_curr_poly->capacity()) + m_curr_poly->reserve(len + buffLength); + m_curr_poly->push_back(pt); +} +//------------------------------------------------------------------------------ + +void DoSquare(double mul = 1.0) +{ + IntPoint pt1 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), + (long64)Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); + IntPoint pt2 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), + (long64)Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); + if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * m_delta >= 0) + { + double a1 = std::atan2(normals[m_k].Y, normals[m_k].X); + double a2 = std::atan2(-normals[m_j].Y, -normals[m_j].X); + a1 = std::fabs(a2 - a1); + if (a1 > pi) a1 = pi * 2 - a1; + double dx = std::tan((pi - a1) / 4) * std::fabs(m_delta * mul); + pt1 = IntPoint((long64)(pt1.X -normals[m_k].Y * dx), + (long64)(pt1.Y + normals[m_k].X * dx)); + AddPoint(pt1); + pt2 = IntPoint((long64)(pt2.X + normals[m_j].Y * dx), + (long64)(pt2.Y -normals[m_j].X * dx)); + AddPoint(pt2); + } + else + { + AddPoint(pt1); + AddPoint(m_p[m_i][m_j]); + AddPoint(pt2); + } +} +//------------------------------------------------------------------------------ + +void DoMiter() +{ + if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * m_delta >= 0) + { + double q = m_delta / m_R; + AddPoint(IntPoint((long64)Round(m_p[m_i][m_j].X + + (normals[m_k].X + normals[m_j].X) * q), + (long64)Round(m_p[m_i][m_j].Y + (normals[m_k].Y + normals[m_j].Y) * q))); + } + else + { + IntPoint pt1 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_k].X * + m_delta), (long64)Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); + IntPoint pt2 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_j].X * + m_delta), (long64)Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); + AddPoint(pt1); + AddPoint(m_p[m_i][m_j]); + AddPoint(pt2); + } +} +//------------------------------------------------------------------------------ + +void DoRound() +{ + IntPoint pt1 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), + (long64)Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); + IntPoint pt2 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), + (long64)Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); + AddPoint(pt1); + //round off reflex angles (ie > 180 deg) unless almost flat (ie < ~10deg). + if ((normals[m_k].X*normals[m_j].Y - normals[m_j].X*normals[m_k].Y) * m_delta >= 0) + { + if (normals[m_j].X * normals[m_k].X + normals[m_j].Y * normals[m_k].Y < 0.985) + { + double a1 = std::atan2(normals[m_k].Y, normals[m_k].X); + double a2 = std::atan2(normals[m_j].Y, normals[m_j].X); + if (m_delta > 0 && a2 < a1) a2 += pi *2; + else if (m_delta < 0 && a2 > a1) a2 -= pi *2; + Polygon arc = BuildArc(m_p[m_i][m_j], a1, a2, m_delta); + for (Polygon::size_type m = 0; m < arc.size(); m++) + AddPoint(arc[m]); + } + } + else + AddPoint(m_p[m_i][m_j]); + AddPoint(pt2); +} +//-------------------------------------------------------------------------- + +bool UpdateBotPt(const IntPoint &pt, IntPoint &botPt) +{ + if (pt.Y > botPt.Y || (pt.Y == botPt.Y && pt.X < botPt.X)) + { + botPt = pt; + return true; + } + else return false; +} +//-------------------------------------------------------------------------- + +}; //end PolyOffsetBuilder + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, + double delta, JoinType jointype, double MiterLimit, bool AutoFix) +{ + if (&out_polys == &in_polys) + { + Polygons poly2(in_polys); + PolyOffsetBuilder(poly2, out_polys, delta, jointype, MiterLimit, AutoFix); + } + else PolyOffsetBuilder(in_polys, out_polys, delta, jointype, MiterLimit, AutoFix); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys, PolyFillType fillType) +{ + Clipper c; + c.AddPolygon(in_poly, ptSubject); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys, PolyFillType fillType) +{ + Clipper c; + c.AddPolygons(in_polys, ptSubject); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(Polygons &polys, PolyFillType fillType) +{ + SimplifyPolygons(polys, polys, fillType); +} +//------------------------------------------------------------------------------ + +void CleanPolygon(Polygon& in_poly, Polygon& out_poly, double distance) +{ + //delta = proximity in units/pixels below which vertices + //will be stripped. Default ~= sqrt(2) so when adjacent + //vertices have both x & y coords within 1 unit, then + //the second vertex will be stripped. + int len = in_poly.size(); + if (len < 3) + out_poly.resize(0); + else + out_poly.resize(in_poly.size()); + + int d = (int)(distance * distance); + IntPoint p = in_poly[0]; + int j = 1; + for (int i = 1; i < len; i++) + { + if ((in_poly[i].X - p.X) * (in_poly[i].X - p.X) + + (in_poly[i].Y - p.Y) * (in_poly[i].Y - p.Y) <= d) + continue; + out_poly[j] = in_poly[i]; + p = in_poly[i]; + j++; + } + p = in_poly[j - 1]; + if ((in_poly[0].X - p.X) * (in_poly[0].X - p.X) + + (in_poly[0].Y - p.Y) * (in_poly[0].Y - p.Y) <= d) + j--; + if (j < len) + out_poly.resize(j); +} +//------------------------------------------------------------------------------ +void CleanPolygons(Polygons& in_polys, Polygons& out_polys, double distance) +{ + for (Polygons::size_type i = 0; i < in_polys.size(); ++i) + CleanPolygon(in_polys[i], out_polys[i], distance); +} +//------------------------------------------------------------------------------ + +void AddPolyNodeToPolygons(PolyNode& polynode, Polygons& polygons) +{ + if (polynode.Contour.size() > 0) + polygons.push_back(polynode.Contour); + for (int i = 0; i < polynode.ChildCount(); ++i) + AddPolyNodeToPolygons(*polynode.Childs[i], polygons); +} +//------------------------------------------------------------------------------ + +void PolyTreeToPolygons(PolyTree& polytree, Polygons& polygons) +{ + polygons.resize(0); + polygons.reserve(polytree.Total()); + AddPolyNodeToPolygons(polytree, polygons); +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, IntPoint& p) +{ + s << p.X << ' ' << p.Y << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, Polygon &p) +{ + for (Polygon::size_type i = 0; i < p.size(); i++) + s << p[i]; + s << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, Polygons &p) +{ + for (Polygons::size_type i = 0; i < p.size(); i++) + s << p[i]; + s << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +} //ClipperLib namespace diff --git a/clipper/clipper.hpp b/clipper/clipper.hpp new file mode 100755 index 0000000..c807efa --- /dev/null +++ b/clipper/clipper.hpp @@ -0,0 +1,340 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 5.1.0 * +* Date : 1 February 2013 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2013 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +#ifndef clipper_hpp +#define clipper_hpp + +#include +#include +#include +#include +#include + +namespace ClipperLib { + +enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; +enum PolyType { ptSubject, ptClip }; +//By far the most widely used winding rules for polygon filling are +//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) +//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) +//see http://glprogramming.com/red/chapter11.html +enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; + +typedef signed long long long64; +typedef unsigned long long ulong64; + +struct IntPoint { +public: + long64 X; + long64 Y; + IntPoint(long64 x = 0, long64 y = 0): X(x), Y(y) {}; + friend std::ostream& operator <<(std::ostream &s, IntPoint &p); +}; + +typedef std::vector< IntPoint > Polygon; +typedef std::vector< Polygon > Polygons; + + +std::ostream& operator <<(std::ostream &s, Polygon &p); +std::ostream& operator <<(std::ostream &s, Polygons &p); + +class PolyNode; +typedef std::vector< PolyNode* > PolyNodes; + +class PolyNode +{ +public: + Polygon Contour; + PolyNodes Childs; + PolyNode* Parent; + PolyNode* GetNext(); + bool IsHole(); + int ChildCount(); +private: + PolyNode* GetNextSiblingUp(); + unsigned Index; //node index in Parent.Childs + void AddChild(PolyNode& child); + friend class Clipper; //to access Index +}; + +class PolyTree: public PolyNode +{ +public: + ~PolyTree(){Clear();}; + PolyNode* GetFirst(); + void Clear(); + int Total(); +private: + PolyNodes AllNodes; + friend class Clipper; //to access AllNodes +}; + +enum JoinType { jtSquare, jtRound, jtMiter }; + +bool Orientation(const Polygon &poly); +double Area(const Polygon &poly); +void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, + double delta, JoinType jointype = jtSquare, double MiterLimit = 2, bool AutoFix = true); + +void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(Polygons &polys, PolyFillType fillType = pftEvenOdd); + +void CleanPolygon(Polygon& in_poly, Polygon& out_poly, double distance = 1.415); +void CleanPolygons(Polygons& in_polys, Polygons& out_polys, double distance = 1.415); + +void PolyTreeToPolygons(PolyTree& polytree, Polygons& polygons); + +void ReversePolygon(Polygon& p); +void ReversePolygons(Polygons& p); + +//used internally ... +enum EdgeSide { esLeft = 1, esRight = 2}; +enum IntersectProtects { ipNone = 0, ipLeft = 1, ipRight = 2, ipBoth = 3 }; + +struct TEdge { + long64 xbot; + long64 ybot; + long64 xcurr; + long64 ycurr; + long64 xtop; + long64 ytop; + double dx; + long64 deltaX; + long64 deltaY; + long64 tmpX; + PolyType polyType; + EdgeSide side; + int windDelta; //1 or -1 depending on winding direction + int windCnt; + int windCnt2; //winding count of the opposite polytype + int outIdx; + TEdge *next; + TEdge *prev; + TEdge *nextInLML; + TEdge *nextInAEL; + TEdge *prevInAEL; + TEdge *nextInSEL; + TEdge *prevInSEL; +}; + +struct IntersectNode { + TEdge *edge1; + TEdge *edge2; + IntPoint pt; + IntersectNode *next; +}; + +struct LocalMinima { + long64 Y; + TEdge *leftBound; + TEdge *rightBound; + LocalMinima *next; +}; + +struct Scanbeam { + long64 Y; + Scanbeam *next; +}; + +struct OutPt; //forward declaration + +struct OutRec { + int idx; + bool isHole; + OutRec *FirstLeft; //see comments in clipper.pas + PolyNode *polyNode; + OutPt *pts; + OutPt *bottomPt; +}; + +struct OutPt { + int idx; + IntPoint pt; + OutPt *next; + OutPt *prev; +}; + +struct JoinRec { + IntPoint pt1a; + IntPoint pt1b; + int poly1Idx; + IntPoint pt2a; + IntPoint pt2b; + int poly2Idx; +}; + +struct HorzJoinRec { + TEdge *edge; + int savedIdx; +}; + +struct IntRect { long64 left; long64 top; long64 right; long64 bottom; }; + +typedef std::vector < OutRec* > PolyOutList; +typedef std::vector < TEdge* > EdgeList; +typedef std::vector < JoinRec* > JoinList; +typedef std::vector < HorzJoinRec* > HorzJoinList; + +//ClipperBase is the ancestor to the Clipper class. It should not be +//instantiated directly. This class simply abstracts the conversion of sets of +//polygon coordinates into edge objects that are stored in a LocalMinima list. +class ClipperBase +{ +public: + ClipperBase(); + virtual ~ClipperBase(); + bool AddPolygon(const Polygon &pg, PolyType polyType); + bool AddPolygons( const Polygons &ppg, PolyType polyType); + virtual void Clear(); + IntRect GetBounds(); +protected: + void DisposeLocalMinimaList(); + TEdge* AddBoundsToLML(TEdge *e); + void PopLocalMinima(); + virtual void Reset(); + void InsertLocalMinima(LocalMinima *newLm); + LocalMinima *m_CurrentLM; + LocalMinima *m_MinimaList; + bool m_UseFullRange; + EdgeList m_edges; +}; + +class Clipper : public virtual ClipperBase +{ +public: + Clipper(); + ~Clipper(); + bool Execute(ClipType clipType, + Polygons &solution, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); + bool Execute(ClipType clipType, + PolyTree &polytree, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); + void Clear(); + bool ReverseSolution() {return m_ReverseOutput;}; + void ReverseSolution(bool value) {m_ReverseOutput = value;}; +protected: + void Reset(); + virtual bool ExecuteInternal(); +private: + PolyOutList m_PolyOuts; + JoinList m_Joins; + HorzJoinList m_HorizJoins; + ClipType m_ClipType; + Scanbeam *m_Scanbeam; + TEdge *m_ActiveEdges; + TEdge *m_SortedEdges; + IntersectNode *m_IntersectNodes; + bool m_ExecuteLocked; + PolyFillType m_ClipFillType; + PolyFillType m_SubjFillType; + bool m_ReverseOutput; + bool m_UsingPolyTree; + void DisposeScanbeamList(); + void SetWindingCount(TEdge& edge); + bool IsEvenOddFillType(const TEdge& edge) const; + bool IsEvenOddAltFillType(const TEdge& edge) const; + void InsertScanbeam(const long64 Y); + long64 PopScanbeam(); + void InsertLocalMinimaIntoAEL(const long64 botY); + void InsertEdgeIntoAEL(TEdge *edge); + void AddEdgeToSEL(TEdge *edge); + void CopyAELToSEL(); + void DeleteFromSEL(TEdge *e); + void DeleteFromAEL(TEdge *e); + void UpdateEdgeIntoAEL(TEdge *&e); + void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); + bool IsContributing(const TEdge& edge) const; + bool IsTopHorz(const long64 XPos); + void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); + void DoMaxima(TEdge *e, long64 topY); + void ProcessHorizontals(); + void ProcessHorizontal(TEdge *horzEdge); + void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + void AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + void AppendPolygon(TEdge *e1, TEdge *e2); + void DoEdge1(TEdge *edge1, TEdge *edge2, const IntPoint &pt); + void DoEdge2(TEdge *edge1, TEdge *edge2, const IntPoint &pt); + void DoBothEdges(TEdge *edge1, TEdge *edge2, const IntPoint &pt); + void IntersectEdges(TEdge *e1, TEdge *e2, + const IntPoint &pt, const IntersectProtects protects); + OutRec* CreateOutRec(); + void AddOutPt(TEdge *e, const IntPoint &pt); + void DisposeAllPolyPts(); + void DisposeOutRec(PolyOutList::size_type index); + bool ProcessIntersections(const long64 botY, const long64 topY); + void AddIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt); + void BuildIntersectList(const long64 botY, const long64 topY); + void ProcessIntersectList(); + void ProcessEdgesAtTopOfScanbeam(const long64 topY); + void BuildResult(Polygons& polys); + void BuildResult2(PolyTree& polytree); + void SetHoleState(TEdge *e, OutRec *OutRec); + void DisposeIntersectNodes(); + bool FixupIntersections(); + void FixupOutPolygon(OutRec &outRec); + bool IsHole(TEdge *e); + void FixHoleLinkage(OutRec &outRec); + void AddJoin(TEdge *e1, TEdge *e2, int e1OutIdx = -1, int e2OutIdx = -1); + void ClearJoins(); + void AddHorzJoin(TEdge *e, int idx); + void ClearHorzJoins(); + bool JoinPoints(const JoinRec *j, OutPt *&p1, OutPt *&p2); + void FixupJoinRecs(JoinRec *j, OutPt *pt, unsigned startIdx); + void JoinCommonEdges(); + void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); + void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec); +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +class clipperException : public std::exception +{ + public: + clipperException(const char* description): m_descr(description) {} + virtual ~clipperException() throw() {} + virtual const char* what() const throw() {return m_descr.c_str();} + private: + std::string m_descr; +}; +//------------------------------------------------------------------------------ + +} //ClipperLib namespace + +#endif //clipper_hpp + + diff --git a/clipper/clipper_ver5.1.0.zip b/clipper/clipper_ver5.1.0.zip new file mode 100755 index 0000000..3cef614 Binary files /dev/null and b/clipper/clipper_ver5.1.0.zip differ diff --git a/clipper/cpp/CMakeLists.txt b/clipper/cpp/CMakeLists.txt new file mode 100755 index 0000000..6dacef1 --- /dev/null +++ b/clipper/cpp/CMakeLists.txt @@ -0,0 +1,15 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6.0) +PROJECT(polyclipping) + +SET(CMAKE_BUILD_TYPE "Release" CACHE STRING "Release type") +SET(CMAKE_INSTALL_LIBDIR lib${LIB_SUFFIX}) + +SET(BUILD_SHARED_LIBS ON CACHE BOOL + "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)") +ADD_LIBRARY(polyclipping clipper.cpp) + +# The header name clipper.hpp is too generic, so install in a subdirectory +INSTALL (FILES clipper.hpp DESTINATION include/polyclipping) +INSTALL (TARGETS polyclipping LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +SET_TARGET_PROPERTIES(polyclipping PROPERTIES VERSION 7.0.0 SOVERSION 7 ) \ No newline at end of file diff --git a/clipper/cpp/clipper.cpp b/clipper/cpp/clipper.cpp new file mode 100755 index 0000000..1042052 --- /dev/null +++ b/clipper/cpp/clipper.cpp @@ -0,0 +1,3469 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 5.1.0 * +* Date : 1 February 2013 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2013 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +/******************************************************************************* +* * +* This is a translation of the Delphi Clipper library and the naming style * +* used has retained a Delphi flavour. * +* * +*******************************************************************************/ + +#include "clipper.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +static long64 const loRange = 0x3FFFFFFF; +static long64 const hiRange = 0x3FFFFFFFFFFFFFFFLL; +static double const pi = 3.141592653589793238; +enum Direction { dRightToLeft, dLeftToRight }; + +#define HORIZONTAL (-1.0E+40) +#define TOLERANCE (1.0e-20) +#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) +#define NEAR_EQUAL(a, b) NEAR_ZERO((a) - (b)) + +inline long64 Abs(long64 val) +{ + return val < 0 ? -val : val; +} + +//------------------------------------------------------------------------------ +// PolyTree methods ... +//------------------------------------------------------------------------------ + +void PolyTree::Clear() +{ + for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) + delete AllNodes[i]; + AllNodes.resize(0); + Childs.resize(0); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyTree::GetFirst() +{ + if (Childs.size() > 0) + return Childs[0]; + else + return 0; +} +//------------------------------------------------------------------------------ + +int PolyTree::Total() +{ + return AllNodes.size(); +} + +//------------------------------------------------------------------------------ +// PolyNode methods ... +//------------------------------------------------------------------------------ + + +int PolyNode::ChildCount() +{ + return Childs.size(); +} +//------------------------------------------------------------------------------ + +void PolyNode::AddChild(PolyNode& child) +{ + unsigned cnt = Childs.size(); + Childs.push_back(&child); + child.Parent = this; + child.Index = cnt; +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNext() +{ + if (Childs.size() > 0) + return Childs[0]; + else + return GetNextSiblingUp(); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNextSiblingUp() +{ + if (!Parent) //protects against PolyTree.GetNextSiblingUp() + return 0; + else if (Index == Parent->Childs.size() - 1) + return Parent->GetNextSiblingUp(); + else + return Parent->Childs[Index + 1]; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsHole() +{ + bool result = true; + PolyNode* node = Parent; + while (node) + { + result = !result; + node = node->Parent; + } + return result; +} + +//------------------------------------------------------------------------------ +// Int128 class (enables safe math on signed 64bit integers) +// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1 +// Int128 val2((long64)9223372036854775807); +// Int128 val3 = val1 * val2; +// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) +//------------------------------------------------------------------------------ + +class Int128 +{ + public: + + ulong64 lo; + long64 hi; + + Int128(long64 _lo = 0) + { + lo = (ulong64)_lo; + if (_lo < 0) hi = -1; else hi = 0; + } + + + Int128(const Int128 &val): lo(val.lo), hi(val.hi){} + + Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} + + long64 operator = (const long64 &val) + { + lo = (ulong64)val; + if (val < 0) hi = -1; else hi = 0; + return val; + } + + bool operator == (const Int128 &val) const + {return (hi == val.hi && lo == val.lo);} + + bool operator != (const Int128 &val) const + { return !(*this == val);} + + bool operator > (const Int128 &val) const + { + if (hi != val.hi) + return hi > val.hi; + else + return lo > val.lo; + } + + bool operator < (const Int128 &val) const + { + if (hi != val.hi) + return hi < val.hi; + else + return lo < val.lo; + } + + bool operator >= (const Int128 &val) const + { return !(*this < val);} + + bool operator <= (const Int128 &val) const + { return !(*this > val);} + + Int128& operator += (const Int128 &rhs) + { + hi += rhs.hi; + lo += rhs.lo; + if (lo < rhs.lo) hi++; + return *this; + } + + Int128 operator + (const Int128 &rhs) const + { + Int128 result(*this); + result+= rhs; + return result; + } + + Int128& operator -= (const Int128 &rhs) + { + *this += -rhs; + return *this; + } + + Int128 operator - (const Int128 &rhs) const + { + Int128 result(*this); + result -= rhs; + return result; + } + + Int128 operator-() const //unary negation + { + if (lo == 0) + return Int128(-hi,0); + else + return Int128(~hi,~lo +1); + } + + Int128 operator/ (const Int128 &rhs) const + { + if (rhs.lo == 0 && rhs.hi == 0) + throw "Int128 operator/: divide by zero"; + + bool negate = (rhs.hi < 0) != (hi < 0); + Int128 dividend = *this; + Int128 divisor = rhs; + if (dividend.hi < 0) dividend = -dividend; + if (divisor.hi < 0) divisor = -divisor; + + if (divisor < dividend) + { + Int128 result = Int128(0); + Int128 cntr = Int128(1); + while (divisor.hi >= 0 && !(divisor > dividend)) + { + divisor.hi <<= 1; + if ((long64)divisor.lo < 0) divisor.hi++; + divisor.lo <<= 1; + + cntr.hi <<= 1; + if ((long64)cntr.lo < 0) cntr.hi++; + cntr.lo <<= 1; + } + divisor.lo >>= 1; + if ((divisor.hi & 1) == 1) + divisor.lo |= 0x8000000000000000LL; + divisor.hi = (ulong64)divisor.hi >> 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000LL; + cntr.hi >>= 1; + + while (cntr.hi != 0 || cntr.lo != 0) + { + if (!(dividend < divisor)) + { + dividend -= divisor; + result.hi |= cntr.hi; + result.lo |= cntr.lo; + } + divisor.lo >>= 1; + if ((divisor.hi & 1) == 1) + divisor.lo |= 0x8000000000000000LL; + divisor.hi >>= 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000LL; + cntr.hi >>= 1; + } + if (negate) result = -result; + return result; + } + else if (rhs.hi == this->hi && rhs.lo == this->lo) + return Int128(1); + else + return Int128(0); + } + + double AsDouble() const + { + const double shift64 = 18446744073709551616.0; //2^64 + if (hi < 0) + { + if (lo == 0) return (double)hi * shift64; + else return -(double)(~lo + ~hi * shift64); + } + else + return (double)(lo + hi * shift64); + } +}; + +Int128 Int128Mul (long64 lhs, long64 rhs) +{ + bool negate = (lhs < 0) != (rhs < 0); + + if (lhs < 0) lhs = -lhs; + ulong64 int1Hi = ulong64(lhs) >> 32; + ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); + + if (rhs < 0) rhs = -rhs; + ulong64 int2Hi = ulong64(rhs) >> 32; + ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); + + //nb: see comments in clipper.pas + ulong64 a = int1Hi * int2Hi; + ulong64 b = int1Lo * int2Lo; + ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + Int128 tmp; + tmp.hi = long64(a + (c >> 32)); + tmp.lo = long64(c << 32); + tmp.lo += long64(b); + if (tmp.lo < b) tmp.hi++; + if (negate) tmp = -tmp; + return tmp; +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +bool FullRangeNeeded(const Polygon &pts) +{ + bool result = false; + for (Polygon::size_type i = 0; i < pts.size(); ++i) + { + if (Abs(pts[i].X) > hiRange || Abs(pts[i].Y) > hiRange) + throw "Coordinate exceeds range bounds."; + else if (Abs(pts[i].X) > loRange || Abs(pts[i].Y) > loRange) + result = true; + } + return result; +} +//------------------------------------------------------------------------------ + +bool Orientation(const Polygon &poly) +{ + return Area(poly) >= 0; +} +//------------------------------------------------------------------------------ + +inline bool PointsEqual( const IntPoint &pt1, const IntPoint &pt2) +{ + return ( pt1.X == pt2.X && pt1.Y == pt2.Y ); +} +//------------------------------------------------------------------------------ + +double Area(const Polygon &poly) +{ + int highI = (int)poly.size() -1; + if (highI < 2) return 0; + + if (FullRangeNeeded(poly)) { + Int128 a; + a = Int128Mul(poly[highI].X + poly[0].X, poly[0].Y - poly[highI].Y); + for (int i = 1; i <= highI; ++i) + a += Int128Mul(poly[i - 1].X + poly[i].X, poly[i].Y - poly[i -1].Y); + return a.AsDouble() / 2; + } + else + { + double a; + a = ((double)poly[highI].X + poly[0].X) * ((double)poly[0].Y - poly[highI].Y); + for (int i = 1; i <= highI; ++i) + a += ((double)poly[i - 1].X + poly[i].X) * ((double)poly[i].Y - poly[i - 1].Y); + return a / 2; + } +} +//------------------------------------------------------------------------------ + +double Area(const OutRec &outRec, bool UseFullInt64Range) +{ + OutPt *op = outRec.pts; + if (!op) return 0; + if (UseFullInt64Range) { + Int128 a(0); + do { + a += Int128Mul(op->pt.X + op->prev->pt.X, op->prev->pt.Y - op->pt.Y); + op = op->next; + } while (op != outRec.pts); + return a.AsDouble() / 2; + } + else + { + double a = 0; + do { + a = a + (op->pt.X + op->prev->pt.X) * (op->prev->pt.Y - op->pt.Y); + op = op->next; + } while (op != outRec.pts); + return a / 2; + } +} +//------------------------------------------------------------------------------ + +bool PointIsVertex(const IntPoint &pt, OutPt *pp) +{ + OutPt *pp2 = pp; + do + { + if (PointsEqual(pp2->pt, pt)) return true; + pp2 = pp2->next; + } + while (pp2 != pp); + return false; +} +//------------------------------------------------------------------------------ + +bool PointInPolygon(const IntPoint &pt, OutPt *pp, bool UseFullInt64Range) +{ + OutPt *pp2 = pp; + bool result = false; + if (UseFullInt64Range) { + do + { + if ((((pp2->pt.Y <= pt.Y) && (pt.Y < pp2->prev->pt.Y)) || + ((pp2->prev->pt.Y <= pt.Y) && (pt.Y < pp2->pt.Y))) && + Int128(pt.X - pp2->pt.X) < + Int128Mul(pp2->prev->pt.X - pp2->pt.X, pt.Y - pp2->pt.Y) / + Int128(pp2->prev->pt.Y - pp2->pt.Y)) + result = !result; + pp2 = pp2->next; + } + while (pp2 != pp); + } + else + { + do + { + if ((((pp2->pt.Y <= pt.Y) && (pt.Y < pp2->prev->pt.Y)) || + ((pp2->prev->pt.Y <= pt.Y) && (pt.Y < pp2->pt.Y))) && + (pt.X < (pp2->prev->pt.X - pp2->pt.X) * (pt.Y - pp2->pt.Y) / + (pp2->prev->pt.Y - pp2->pt.Y) + pp2->pt.X )) result = !result; + pp2 = pp2->next; + } + while (pp2 != pp); + } + return result; +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(TEdge &e1, TEdge &e2, bool UseFullInt64Range) +{ + if (UseFullInt64Range) + return Int128Mul(e1.deltaY, e2.deltaX) == Int128Mul(e1.deltaX, e2.deltaY); + else return e1.deltaY * e2.deltaX == e1.deltaX * e2.deltaY; +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, bool UseFullInt64Range) +{ + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); + else return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) +{ + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); + else return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); +} +//------------------------------------------------------------------------------ + +double GetDx(const IntPoint pt1, const IntPoint pt2) +{ + return (pt1.Y == pt2.Y) ? + HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); +} +//--------------------------------------------------------------------------- + +void SetDx(TEdge &e) +{ + e.deltaX = (e.xtop - e.xbot); + e.deltaY = (e.ytop - e.ybot); + + if (e.deltaY == 0) e.dx = HORIZONTAL; + else e.dx = (double)(e.deltaX) / e.deltaY; +} +//--------------------------------------------------------------------------- + +void SwapSides(TEdge &edge1, TEdge &edge2) +{ + EdgeSide side = edge1.side; + edge1.side = edge2.side; + edge2.side = side; +} +//------------------------------------------------------------------------------ + +void SwapPolyIndexes(TEdge &edge1, TEdge &edge2) +{ + int outIdx = edge1.outIdx; + edge1.outIdx = edge2.outIdx; + edge2.outIdx = outIdx; +} +//------------------------------------------------------------------------------ + +inline long64 Round(double val) +{ + return (val < 0) ? static_cast(val - 0.5) : static_cast(val + 0.5); +} +//------------------------------------------------------------------------------ + +long64 TopX(TEdge &edge, const long64 currentY) +{ + return ( currentY == edge.ytop ) ? + edge.xtop : edge.xbot + Round(edge.dx *(currentY - edge.ybot)); +} +//------------------------------------------------------------------------------ + +bool IntersectPoint(TEdge &edge1, TEdge &edge2, + IntPoint &ip, bool UseFullInt64Range) +{ + double b1, b2; + if (SlopesEqual(edge1, edge2, UseFullInt64Range)) return false; + else if (NEAR_ZERO(edge1.dx)) + { + ip.X = edge1.xbot; + if (NEAR_EQUAL(edge2.dx, HORIZONTAL)) + { + ip.Y = edge2.ybot; + } else + { + b2 = edge2.ybot - (edge2.xbot / edge2.dx); + ip.Y = Round(ip.X / edge2.dx + b2); + } + } + else if (NEAR_ZERO(edge2.dx)) + { + ip.X = edge2.xbot; + if (NEAR_EQUAL(edge1.dx, HORIZONTAL)) + { + ip.Y = edge1.ybot; + } else + { + b1 = edge1.ybot - (edge1.xbot / edge1.dx); + ip.Y = Round(ip.X / edge1.dx + b1); + } + } else + { + b1 = edge1.xbot - edge1.ybot * edge1.dx; + b2 = edge2.xbot - edge2.ybot * edge2.dx; + double q = (b2-b1) / (edge1.dx - edge2.dx); + ip.Y = Round(q); + if (std::fabs(edge1.dx) < std::fabs(edge2.dx)) + ip.X = Round(edge1.dx * q + b1); + else + ip.X = Round(edge2.dx * q + b2); + } + + if (ip.Y < edge1.ytop || ip.Y < edge2.ytop) + { + if (edge1.ytop > edge2.ytop) + { + ip.X = edge1.xtop; + ip.Y = edge1.ytop; + return TopX(edge2, edge1.ytop) < edge1.xtop; + } else + { + ip.X = edge2.xtop; + ip.Y = edge2.ytop; + return TopX(edge1, edge2.ytop) > edge2.xtop; + } + } + else + return true; +} +//------------------------------------------------------------------------------ + +void ReversePolyPtLinks(OutPt *pp) +{ + if (!pp) return; + OutPt *pp1, *pp2; + pp1 = pp; + do { + pp2 = pp1->next; + pp1->next = pp1->prev; + pp1->prev = pp2; + pp1 = pp2; + } while( pp1 != pp ); +} +//------------------------------------------------------------------------------ + +void DisposeOutPts(OutPt*& pp) +{ + if (pp == 0) return; + pp->prev->next = 0; + while( pp ) + { + OutPt *tmpPp = pp; + pp = pp->next; + delete tmpPp ; + } +} +//------------------------------------------------------------------------------ + +void InitEdge(TEdge *e, TEdge *eNext, + TEdge *ePrev, const IntPoint &pt, PolyType polyType) +{ + std::memset( e, 0, sizeof( TEdge )); + + e->next = eNext; + e->prev = ePrev; + e->xcurr = pt.X; + e->ycurr = pt.Y; + if (e->ycurr >= e->next->ycurr) + { + e->xbot = e->xcurr; + e->ybot = e->ycurr; + e->xtop = e->next->xcurr; + e->ytop = e->next->ycurr; + e->windDelta = 1; + } else + { + e->xtop = e->xcurr; + e->ytop = e->ycurr; + e->xbot = e->next->xcurr; + e->ybot = e->next->ycurr; + e->windDelta = -1; + } + SetDx(*e); + e->polyType = polyType; + e->outIdx = -1; +} +//------------------------------------------------------------------------------ + +inline void SwapX(TEdge &e) +{ + //swap horizontal edges' top and bottom x's so they follow the natural + //progression of the bounds - ie so their xbots will align with the + //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] + e.xcurr = e.xtop; + e.xtop = e.xbot; + e.xbot = e.xcurr; +} +//------------------------------------------------------------------------------ + +void SwapPoints(IntPoint &pt1, IntPoint &pt2) +{ + IntPoint tmp = pt1; + pt1 = pt2; + pt2 = tmp; +} +//------------------------------------------------------------------------------ + +bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, + IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) +{ + //precondition: segments are colinear. + if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) + { + if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); + if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); + if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; + return pt1.X < pt2.X; + } else + { + if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b); + if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b); + if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; + return pt1.Y > pt2.Y; + } +} +//------------------------------------------------------------------------------ + +bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) +{ + OutPt *p = btmPt1->prev; + while (PointsEqual(p->pt, btmPt1->pt) && (p != btmPt1)) p = p->prev; + double dx1p = std::fabs(GetDx(btmPt1->pt, p->pt)); + p = btmPt1->next; + while (PointsEqual(p->pt, btmPt1->pt) && (p != btmPt1)) p = p->next; + double dx1n = std::fabs(GetDx(btmPt1->pt, p->pt)); + + p = btmPt2->prev; + while (PointsEqual(p->pt, btmPt2->pt) && (p != btmPt2)) p = p->prev; + double dx2p = std::fabs(GetDx(btmPt2->pt, p->pt)); + p = btmPt2->next; + while (PointsEqual(p->pt, btmPt2->pt) && (p != btmPt2)) p = p->next; + double dx2n = std::fabs(GetDx(btmPt2->pt, p->pt)); + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); +} +//------------------------------------------------------------------------------ + +OutPt* GetBottomPt(OutPt *pp) +{ + OutPt* dups = 0; + OutPt* p = pp->next; + while (p != pp) + { + if (p->pt.Y > pp->pt.Y) + { + pp = p; + dups = 0; + } + else if (p->pt.Y == pp->pt.Y && p->pt.X <= pp->pt.X) + { + if (p->pt.X < pp->pt.X) + { + dups = 0; + pp = p; + } else + { + if (p->next != pp && p->prev != pp) dups = p; + } + } + p = p->next; + } + if (dups) + { + //there appears to be at least 2 vertices at bottomPt so ... + while (dups != p) + { + if (!FirstIsBottomPt(p, dups)) pp = dups; + dups = dups->next; + while (!PointsEqual(dups->pt, pp->pt)) dups = dups->next; + } + } + return pp; +} +//------------------------------------------------------------------------------ + +bool FindSegment(OutPt* &pp, IntPoint &pt1, IntPoint &pt2) +{ + //outPt1 & outPt2 => the overlap segment (if the function returns true) + if (!pp) return false; + OutPt* pp2 = pp; + IntPoint pt1a = pt1, pt2a = pt2; + do + { + if (SlopesEqual(pt1a, pt2a, pp->pt, pp->prev->pt, true) && + SlopesEqual(pt1a, pt2a, pp->pt, true) && + GetOverlapSegment(pt1a, pt2a, pp->pt, pp->prev->pt, pt1, pt2)) + return true; + pp = pp->next; + } + while (pp != pp2); + return false; +} +//------------------------------------------------------------------------------ + +bool Pt3IsBetweenPt1AndPt2(const IntPoint pt1, + const IntPoint pt2, const IntPoint pt3) +{ + if (PointsEqual(pt1, pt3) || PointsEqual(pt2, pt3)) return true; + else if (pt1.X != pt2.X) return (pt1.X < pt3.X) == (pt3.X < pt2.X); + else return (pt1.Y < pt3.Y) == (pt3.Y < pt2.Y); +} +//------------------------------------------------------------------------------ + +OutPt* InsertPolyPtBetween(OutPt* p1, OutPt* p2, const IntPoint pt) +{ + if (p1 == p2) throw "JoinError"; + OutPt* result = new OutPt; + result->pt = pt; + if (p2 == p1->next) + { + p1->next = result; + p2->prev = result; + result->next = p2; + result->prev = p1; + } else + { + p2->next = result; + p1->prev = result; + result->next = p1; + result->prev = p2; + } + return result; +} + +//------------------------------------------------------------------------------ +// ClipperBase class methods ... +//------------------------------------------------------------------------------ + +ClipperBase::ClipperBase() //constructor +{ + m_MinimaList = 0; + m_CurrentLM = 0; + m_UseFullRange = true; +} +//------------------------------------------------------------------------------ + +ClipperBase::~ClipperBase() //destructor +{ + Clear(); +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPolygon( const Polygon &pg, PolyType polyType) +{ + int len = (int)pg.size(); + if (len < 3) return false; + + Polygon p(len); + p[0] = pg[0]; + int j = 0; + + long64 maxVal; + if (m_UseFullRange) maxVal = hiRange; else maxVal = loRange; + + for (int i = 0; i < len; ++i) + { + if (Abs(pg[i].X) > maxVal || Abs(pg[i].Y) > maxVal) + { + if (Abs(pg[i].X) > hiRange || Abs(pg[i].Y) > hiRange) + throw "Coordinate exceeds range bounds"; + maxVal = hiRange; + m_UseFullRange = true; + } + + if (i == 0 || PointsEqual(p[j], pg[i])) continue; + else if (j > 0 && SlopesEqual(p[j-1], p[j], pg[i], m_UseFullRange)) + { + if (PointsEqual(p[j-1], pg[i])) j--; + } else j++; + p[j] = pg[i]; + } + if (j < 2) return false; + + len = j+1; + while (len > 2) + { + //nb: test for point equality before testing slopes ... + if (PointsEqual(p[j], p[0])) j--; + else if (PointsEqual(p[0], p[1]) || + SlopesEqual(p[j], p[0], p[1], m_UseFullRange)) + p[0] = p[j--]; + else if (SlopesEqual(p[j-1], p[j], p[0], m_UseFullRange)) j--; + else if (SlopesEqual(p[0], p[1], p[2], m_UseFullRange)) + { + for (int i = 2; i <= j; ++i) p[i-1] = p[i]; + j--; + } + else break; + len--; + } + if (len < 3) return false; + + //create a new edge array ... + TEdge *edges = new TEdge [len]; + m_edges.push_back(edges); + + //convert vertices to a double-linked-list of edges and initialize ... + edges[0].xcurr = p[0].X; + edges[0].ycurr = p[0].Y; + InitEdge(&edges[len-1], &edges[0], &edges[len-2], p[len-1], polyType); + for (int i = len-2; i > 0; --i) + InitEdge(&edges[i], &edges[i+1], &edges[i-1], p[i], polyType); + InitEdge(&edges[0], &edges[1], &edges[len-1], p[0], polyType); + + //reset xcurr & ycurr and find 'eHighest' (given the Y axis coordinates + //increase downward so the 'highest' edge will have the smallest ytop) ... + TEdge *e = &edges[0]; + TEdge *eHighest = e; + do + { + e->xcurr = e->xbot; + e->ycurr = e->ybot; + if (e->ytop < eHighest->ytop) eHighest = e; + e = e->next; + } + while ( e != &edges[0]); + + //make sure eHighest is positioned so the following loop works safely ... + if (eHighest->windDelta > 0) eHighest = eHighest->next; + if (NEAR_EQUAL(eHighest->dx, HORIZONTAL)) eHighest = eHighest->next; + + //finally insert each local minima ... + e = eHighest; + do { + e = AddBoundsToLML(e); + } + while( e != eHighest ); + return true; +} +//------------------------------------------------------------------------------ + +void ClipperBase::InsertLocalMinima(LocalMinima *newLm) +{ + if( ! m_MinimaList ) + { + m_MinimaList = newLm; + } + else if( newLm->Y >= m_MinimaList->Y ) + { + newLm->next = m_MinimaList; + m_MinimaList = newLm; + } else + { + LocalMinima* tmpLm = m_MinimaList; + while( tmpLm->next && ( newLm->Y < tmpLm->next->Y ) ) + tmpLm = tmpLm->next; + newLm->next = tmpLm->next; + tmpLm->next = newLm; + } +} +//------------------------------------------------------------------------------ + +TEdge* ClipperBase::AddBoundsToLML(TEdge *e) +{ + //Starting at the top of one bound we progress to the bottom where there's + //a local minima. We then go to the top of the next bound. These two bounds + //form the left and right (or right and left) bounds of the local minima. + e->nextInLML = 0; + e = e->next; + for (;;) + { + if (NEAR_EQUAL(e->dx, HORIZONTAL)) + { + //nb: proceed through horizontals when approaching from their right, + // but break on horizontal minima if approaching from their left. + // This ensures 'local minima' are always on the left of horizontals. + if (e->next->ytop < e->ytop && e->next->xbot > e->prev->xbot) break; + if (e->xtop != e->prev->xbot) SwapX(*e); + e->nextInLML = e->prev; + } + else if (e->ycurr == e->prev->ycurr) break; + else e->nextInLML = e->prev; + e = e->next; + } + + //e and e.prev are now at a local minima ... + LocalMinima* newLm = new LocalMinima; + newLm->next = 0; + newLm->Y = e->prev->ybot; + + if ( NEAR_EQUAL(e->dx, HORIZONTAL) ) //horizontal edges never start a left bound + { + if (e->xbot != e->prev->xbot) SwapX(*e); + newLm->leftBound = e->prev; + newLm->rightBound = e; + } else if (e->dx < e->prev->dx) + { + newLm->leftBound = e->prev; + newLm->rightBound = e; + } else + { + newLm->leftBound = e; + newLm->rightBound = e->prev; + } + newLm->leftBound->side = esLeft; + newLm->rightBound->side = esRight; + InsertLocalMinima( newLm ); + + for (;;) + { + if ( e->next->ytop == e->ytop && !NEAR_EQUAL(e->next->dx, HORIZONTAL) ) break; + e->nextInLML = e->next; + e = e->next; + if ( NEAR_EQUAL(e->dx, HORIZONTAL) && e->xbot != e->prev->xtop) SwapX(*e); + } + return e->next; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPolygons(const Polygons &ppg, PolyType polyType) +{ + bool result = false; + for (Polygons::size_type i = 0; i < ppg.size(); ++i) + if (AddPolygon(ppg[i], polyType)) result = true; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Clear() +{ + DisposeLocalMinimaList(); + for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) delete [] m_edges[i]; + m_edges.clear(); + m_UseFullRange = false; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Reset() +{ + m_CurrentLM = m_MinimaList; + if( !m_CurrentLM ) return; //ie nothing to process + + //reset all edges ... + LocalMinima* lm = m_MinimaList; + while( lm ) + { + TEdge* e = lm->leftBound; + while( e ) + { + e->xcurr = e->xbot; + e->ycurr = e->ybot; + e->side = esLeft; + e->outIdx = -1; + e = e->nextInLML; + } + e = lm->rightBound; + while( e ) + { + e->xcurr = e->xbot; + e->ycurr = e->ybot; + e->side = esRight; + e->outIdx = -1; + e = e->nextInLML; + } + lm = lm->next; + } +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeLocalMinimaList() +{ + while( m_MinimaList ) + { + LocalMinima* tmpLm = m_MinimaList->next; + delete m_MinimaList; + m_MinimaList = tmpLm; + } + m_CurrentLM = 0; +} +//------------------------------------------------------------------------------ + +void ClipperBase::PopLocalMinima() +{ + if( ! m_CurrentLM ) return; + m_CurrentLM = m_CurrentLM->next; +} +//------------------------------------------------------------------------------ + +IntRect ClipperBase::GetBounds() +{ + IntRect result; + LocalMinima* lm = m_MinimaList; + if (!lm) + { + result.left = result.top = result.right = result.bottom = 0; + return result; + } + result.left = lm->leftBound->xbot; + result.top = lm->leftBound->ybot; + result.right = lm->leftBound->xbot; + result.bottom = lm->leftBound->ybot; + while (lm) + { + if (lm->leftBound->ybot > result.bottom) + result.bottom = lm->leftBound->ybot; + TEdge* e = lm->leftBound; + for (;;) { + TEdge* bottomE = e; + while (e->nextInLML) + { + if (e->xbot < result.left) result.left = e->xbot; + if (e->xbot > result.right) result.right = e->xbot; + e = e->nextInLML; + } + if (e->xbot < result.left) result.left = e->xbot; + if (e->xbot > result.right) result.right = e->xbot; + if (e->xtop < result.left) result.left = e->xtop; + if (e->xtop > result.right) result.right = e->xtop; + if (e->ytop < result.top) result.top = e->ytop; + + if (bottomE == lm->leftBound) e = lm->rightBound; + else break; + } + lm = lm->next; + } + return result; +} + + +//------------------------------------------------------------------------------ +// TClipper methods ... +//------------------------------------------------------------------------------ + +Clipper::Clipper() : ClipperBase() //constructor +{ + m_Scanbeam = 0; + m_ActiveEdges = 0; + m_SortedEdges = 0; + m_IntersectNodes = 0; + m_ExecuteLocked = false; + m_UseFullRange = false; + m_ReverseOutput = false; +} +//------------------------------------------------------------------------------ + +Clipper::~Clipper() //destructor +{ + Clear(); + DisposeScanbeamList(); +} +//------------------------------------------------------------------------------ + +void Clipper::Clear() +{ + if (m_edges.size() == 0) return; //avoids problems with ClipperBase destructor + DisposeAllPolyPts(); + ClipperBase::Clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeScanbeamList() +{ + while ( m_Scanbeam ) { + Scanbeam* sb2 = m_Scanbeam->next; + delete m_Scanbeam; + m_Scanbeam = sb2; + } +} +//------------------------------------------------------------------------------ + +void Clipper::Reset() +{ + ClipperBase::Reset(); + m_Scanbeam = 0; + m_ActiveEdges = 0; + m_SortedEdges = 0; + DisposeAllPolyPts(); + LocalMinima* lm = m_MinimaList; + while (lm) + { + InsertScanbeam(lm->Y); + InsertScanbeam(lm->leftBound->ytop); + lm = lm->next; + } +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, Polygons &solution, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + m_ExecuteLocked = true; + solution.resize(0); + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = false; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult(solution); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree& polytree, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + m_ExecuteLocked = true; + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = true; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult2(polytree); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::FixHoleLinkage(OutRec &outRec) +{ + //skip OutRecs that (a) contain outermost polygons or + //(b) already have the correct owner/child linkage ... + if (!outRec.FirstLeft || + (outRec.isHole != outRec.FirstLeft->isHole && + outRec.FirstLeft->pts)) return; + + OutRec* orfl = outRec.FirstLeft; + while (orfl && ((orfl->isHole == outRec.isHole) || !orfl->pts)) + orfl = orfl->FirstLeft; + outRec.FirstLeft = orfl; +} +//------------------------------------------------------------------------------ + +bool Clipper::ExecuteInternal() +{ + bool succeeded; + try { + Reset(); + if (!m_CurrentLM ) return true; + long64 botY = PopScanbeam(); + do { + InsertLocalMinimaIntoAEL(botY); + ClearHorzJoins(); + ProcessHorizontals(); + long64 topY = PopScanbeam(); + succeeded = ProcessIntersections(botY, topY); + if (!succeeded) break; + ProcessEdgesAtTopOfScanbeam(topY); + botY = topY; + } while( m_Scanbeam ); + } + catch(...) { + succeeded = false; + } + + if (succeeded) + { + //tidy up output polygons and fix orientations where necessary ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->pts) continue; + FixupOutPolygon(*outRec); + if (!outRec->pts) continue; + + if ((outRec->isHole ^ m_ReverseOutput) == (Area(*outRec, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec->pts); + } + + if (m_Joins.size() > 0) JoinCommonEdges(); + } + + ClearJoins(); + ClearHorzJoins(); + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::InsertScanbeam(const long64 Y) +{ + if( !m_Scanbeam ) + { + m_Scanbeam = new Scanbeam; + m_Scanbeam->next = 0; + m_Scanbeam->Y = Y; + } + else if( Y > m_Scanbeam->Y ) + { + Scanbeam* newSb = new Scanbeam; + newSb->Y = Y; + newSb->next = m_Scanbeam; + m_Scanbeam = newSb; + } else + { + Scanbeam* sb2 = m_Scanbeam; + while( sb2->next && ( Y <= sb2->next->Y ) ) sb2 = sb2->next; + if( Y == sb2->Y ) return; //ie ignores duplicates + Scanbeam* newSb = new Scanbeam; + newSb->Y = Y; + newSb->next = sb2->next; + sb2->next = newSb; + } +} +//------------------------------------------------------------------------------ + +long64 Clipper::PopScanbeam() +{ + long64 Y = m_Scanbeam->Y; + Scanbeam* sb2 = m_Scanbeam; + m_Scanbeam = m_Scanbeam->next; + delete sb2; + return Y; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeAllPolyPts(){ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + DisposeOutRec(i); + m_PolyOuts.clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeOutRec(PolyOutList::size_type index) +{ + OutRec *outRec = m_PolyOuts[index]; + if (outRec->pts) DisposeOutPts(outRec->pts); + delete outRec; + m_PolyOuts[index] = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::SetWindingCount(TEdge &edge) +{ + TEdge *e = edge.prevInAEL; + //find the edge of the same polytype that immediately preceeds 'edge' in AEL + while ( e && e->polyType != edge.polyType ) e = e->prevInAEL; + if ( !e ) + { + edge.windCnt = edge.windDelta; + edge.windCnt2 = 0; + e = m_ActiveEdges; //ie get ready to calc windCnt2 + } else if ( IsEvenOddFillType(edge) ) + { + //EvenOdd filling ... + edge.windCnt = 1; + edge.windCnt2 = e->windCnt2; + e = e->nextInAEL; //ie get ready to calc windCnt2 + } else + { + //nonZero, Positive or Negative filling ... + if ( e->windCnt * e->windDelta < 0 ) + { + if (Abs(e->windCnt) > 1) + { + if (e->windDelta * edge.windDelta < 0) edge.windCnt = e->windCnt; + else edge.windCnt = e->windCnt + edge.windDelta; + } else + edge.windCnt = e->windCnt + e->windDelta + edge.windDelta; + } else + { + if ( Abs(e->windCnt) > 1 && e->windDelta * edge.windDelta < 0) + edge.windCnt = e->windCnt; + else if ( e->windCnt + edge.windDelta == 0 ) + edge.windCnt = e->windCnt; + else edge.windCnt = e->windCnt + edge.windDelta; + } + edge.windCnt2 = e->windCnt2; + e = e->nextInAEL; //ie get ready to calc windCnt2 + } + + //update windCnt2 ... + if ( IsEvenOddAltFillType(edge) ) + { + //EvenOdd filling ... + while ( e != &edge ) + { + edge.windCnt2 = (edge.windCnt2 == 0) ? 1 : 0; + e = e->nextInAEL; + } + } else + { + //nonZero, Positive or Negative filling ... + while ( e != &edge ) + { + edge.windCnt2 += e->windDelta; + e = e->nextInAEL; + } + } +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddFillType(const TEdge& edge) const +{ + if (edge.polyType == ptSubject) + return m_SubjFillType == pftEvenOdd; else + return m_ClipFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const +{ + if (edge.polyType == ptSubject) + return m_ClipFillType == pftEvenOdd; else + return m_SubjFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsContributing(const TEdge& edge) const +{ + PolyFillType pft, pft2; + if (edge.polyType == ptSubject) + { + pft = m_SubjFillType; + pft2 = m_ClipFillType; + } else + { + pft = m_ClipFillType; + pft2 = m_SubjFillType; + } + + switch(pft) + { + case pftEvenOdd: + case pftNonZero: + if (Abs(edge.windCnt) != 1) return false; + break; + case pftPositive: + if (edge.windCnt != 1) return false; + break; + default: //pftNegative + if (edge.windCnt != -1) return false; + } + + switch(m_ClipType) + { + case ctIntersection: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.windCnt2 != 0); + case pftPositive: + return (edge.windCnt2 > 0); + default: + return (edge.windCnt2 < 0); + } + case ctUnion: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.windCnt2 == 0); + case pftPositive: + return (edge.windCnt2 <= 0); + default: + return (edge.windCnt2 >= 0); + } + case ctDifference: + if (edge.polyType == ptSubject) + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.windCnt2 == 0); + case pftPositive: + return (edge.windCnt2 <= 0); + default: + return (edge.windCnt2 >= 0); + } + else + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.windCnt2 != 0); + case pftPositive: + return (edge.windCnt2 > 0); + default: + return (edge.windCnt2 < 0); + } + default: + return true; + } +} +//------------------------------------------------------------------------------ + +void Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt) +{ + TEdge *e, *prevE; + if( NEAR_EQUAL(e2->dx, HORIZONTAL) || ( e1->dx > e2->dx ) ) + { + AddOutPt( e1, pt ); + e2->outIdx = e1->outIdx; + e1->side = esLeft; + e2->side = esRight; + e = e1; + if (e->prevInAEL == e2) + prevE = e2->prevInAEL; + else + prevE = e->prevInAEL; + } else + { + AddOutPt( e2, pt ); + e1->outIdx = e2->outIdx; + e1->side = esRight; + e2->side = esLeft; + e = e2; + if (e->prevInAEL == e1) + prevE = e1->prevInAEL; + else + prevE = e->prevInAEL; + } + if (prevE && prevE->outIdx >= 0 && + (TopX(*prevE, pt.Y) == TopX(*e, pt.Y)) && + SlopesEqual(*e, *prevE, m_UseFullRange)) + AddJoin(e, prevE, -1, -1); +} +//------------------------------------------------------------------------------ + +void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt) +{ + AddOutPt( e1, pt ); + if( e1->outIdx == e2->outIdx ) + { + e1->outIdx = -1; + e2->outIdx = -1; + } + else if (e1->outIdx < e2->outIdx) + AppendPolygon(e1, e2); + else + AppendPolygon(e2, e1); +} +//------------------------------------------------------------------------------ + +void Clipper::AddEdgeToSEL(TEdge *edge) +{ + //SEL pointers in PEdge are reused to build a list of horizontal edges. + //However, we don't need to worry about order with horizontal edge processing. + if( !m_SortedEdges ) + { + m_SortedEdges = edge; + edge->prevInSEL = 0; + edge->nextInSEL = 0; + } + else + { + edge->nextInSEL = m_SortedEdges; + edge->prevInSEL = 0; + m_SortedEdges->prevInSEL = edge; + m_SortedEdges = edge; + } +} +//------------------------------------------------------------------------------ + +void Clipper::CopyAELToSEL() +{ + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while ( e ) + { + e->prevInSEL = e->prevInAEL; + e->nextInSEL = e->nextInAEL; + e = e->nextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::AddJoin(TEdge *e1, TEdge *e2, int e1OutIdx, int e2OutIdx) +{ + JoinRec* jr = new JoinRec; + if (e1OutIdx >= 0) + jr->poly1Idx = e1OutIdx; else + jr->poly1Idx = e1->outIdx; + jr->pt1a = IntPoint(e1->xcurr, e1->ycurr); + jr->pt1b = IntPoint(e1->xtop, e1->ytop); + if (e2OutIdx >= 0) + jr->poly2Idx = e2OutIdx; else + jr->poly2Idx = e2->outIdx; + jr->pt2a = IntPoint(e2->xcurr, e2->ycurr); + jr->pt2b = IntPoint(e2->xtop, e2->ytop); + m_Joins.push_back(jr); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearJoins() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + delete m_Joins[i]; + m_Joins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::AddHorzJoin(TEdge *e, int idx) +{ + HorzJoinRec* hj = new HorzJoinRec; + hj->edge = e; + hj->savedIdx = idx; + m_HorizJoins.push_back(hj); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearHorzJoins() +{ + for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); i++) + delete m_HorizJoins[i]; + m_HorizJoins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertLocalMinimaIntoAEL(const long64 botY) +{ + while( m_CurrentLM && ( m_CurrentLM->Y == botY ) ) + { + TEdge* lb = m_CurrentLM->leftBound; + TEdge* rb = m_CurrentLM->rightBound; + + InsertEdgeIntoAEL( lb ); + InsertScanbeam( lb->ytop ); + InsertEdgeIntoAEL( rb ); + + if (IsEvenOddFillType(*lb)) + { + lb->windDelta = 1; + rb->windDelta = 1; + } + else + { + rb->windDelta = -lb->windDelta; + } + SetWindingCount( *lb ); + rb->windCnt = lb->windCnt; + rb->windCnt2 = lb->windCnt2; + + if( NEAR_EQUAL(rb->dx, HORIZONTAL) ) + { + //nb: only rightbounds can have a horizontal bottom edge + AddEdgeToSEL( rb ); + InsertScanbeam( rb->nextInLML->ytop ); + } + else + InsertScanbeam( rb->ytop ); + + if( IsContributing(*lb) ) + AddLocalMinPoly( lb, rb, IntPoint(lb->xcurr, m_CurrentLM->Y) ); + + //if any output polygons share an edge, they'll need joining later ... + if (rb->outIdx >= 0 && NEAR_EQUAL(rb->dx, HORIZONTAL)) + { + for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) + { + IntPoint pt, pt2; //returned by GetOverlapSegment() but unused here. + HorzJoinRec* hj = m_HorizJoins[i]; + //if horizontals rb and hj.edge overlap, flag for joining later ... + if (GetOverlapSegment(IntPoint(hj->edge->xbot, hj->edge->ybot), + IntPoint(hj->edge->xtop, hj->edge->ytop), + IntPoint(rb->xbot, rb->ybot), + IntPoint(rb->xtop, rb->ytop), pt, pt2)) + AddJoin(hj->edge, rb, hj->savedIdx); + } + } + + if( lb->nextInAEL != rb ) + { + if (rb->outIdx >= 0 && rb->prevInAEL->outIdx >= 0 && + SlopesEqual(*rb->prevInAEL, *rb, m_UseFullRange)) + AddJoin(rb, rb->prevInAEL); + + TEdge* e = lb->nextInAEL; + IntPoint pt = IntPoint(lb->xcurr, lb->ycurr); + while( e != rb ) + { + if(!e) throw clipperException("InsertLocalMinimaIntoAEL: missing rightbound!"); + //nb: For calculating winding counts etc, IntersectEdges() assumes + //that param1 will be to the right of param2 ABOVE the intersection ... + IntersectEdges( rb , e , pt , ipNone); //order important here + e = e->nextInAEL; + } + } + PopLocalMinima(); + } +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromAEL(TEdge *e) +{ + TEdge* AelPrev = e->prevInAEL; + TEdge* AelNext = e->nextInAEL; + if( !AelPrev && !AelNext && (e != m_ActiveEdges) ) return; //already deleted + if( AelPrev ) AelPrev->nextInAEL = AelNext; + else m_ActiveEdges = AelNext; + if( AelNext ) AelNext->prevInAEL = AelPrev; + e->nextInAEL = 0; + e->prevInAEL = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromSEL(TEdge *e) +{ + TEdge* SelPrev = e->prevInSEL; + TEdge* SelNext = e->nextInSEL; + if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted + if( SelPrev ) SelPrev->nextInSEL = SelNext; + else m_SortedEdges = SelNext; + if( SelNext ) SelNext->prevInSEL = SelPrev; + e->nextInSEL = 0; + e->prevInSEL = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, + const IntPoint &pt, const IntersectProtects protects) +{ + //e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before + //e2 in AEL except when e1 is being inserted at the intersection point ... + bool e1stops = !(ipLeft & protects) && !e1->nextInLML && + e1->xtop == pt.X && e1->ytop == pt.Y; + bool e2stops = !(ipRight & protects) && !e2->nextInLML && + e2->xtop == pt.X && e2->ytop == pt.Y; + bool e1Contributing = ( e1->outIdx >= 0 ); + bool e2contributing = ( e2->outIdx >= 0 ); + + //update winding counts... + //assumes that e1 will be to the right of e2 ABOVE the intersection + if ( e1->polyType == e2->polyType ) + { + if ( IsEvenOddFillType( *e1) ) + { + int oldE1WindCnt = e1->windCnt; + e1->windCnt = e2->windCnt; + e2->windCnt = oldE1WindCnt; + } else + { + if (e1->windCnt + e2->windDelta == 0 ) e1->windCnt = -e1->windCnt; + else e1->windCnt += e2->windDelta; + if ( e2->windCnt - e1->windDelta == 0 ) e2->windCnt = -e2->windCnt; + else e2->windCnt -= e1->windDelta; + } + } else + { + if (!IsEvenOddFillType(*e2)) e1->windCnt2 += e2->windDelta; + else e1->windCnt2 = ( e1->windCnt2 == 0 ) ? 1 : 0; + if (!IsEvenOddFillType(*e1)) e2->windCnt2 -= e1->windDelta; + else e2->windCnt2 = ( e2->windCnt2 == 0 ) ? 1 : 0; + } + + PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; + if (e1->polyType == ptSubject) + { + e1FillType = m_SubjFillType; + e1FillType2 = m_ClipFillType; + } else + { + e1FillType = m_ClipFillType; + e1FillType2 = m_SubjFillType; + } + if (e2->polyType == ptSubject) + { + e2FillType = m_SubjFillType; + e2FillType2 = m_ClipFillType; + } else + { + e2FillType = m_ClipFillType; + e2FillType2 = m_SubjFillType; + } + + long64 e1Wc, e2Wc; + switch (e1FillType) + { + case pftPositive: e1Wc = e1->windCnt; break; + case pftNegative: e1Wc = -e1->windCnt; break; + default: e1Wc = Abs(e1->windCnt); + } + switch(e2FillType) + { + case pftPositive: e2Wc = e2->windCnt; break; + case pftNegative: e2Wc = -e2->windCnt; break; + default: e2Wc = Abs(e2->windCnt); + } + + if ( e1Contributing && e2contributing ) + { + if ( e1stops || e2stops || + (e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + (e1->polyType != e2->polyType && m_ClipType != ctXor) ) + AddLocalMaxPoly(e1, e2, pt); + else + DoBothEdges( e1, e2, pt ); + } + else if ( e1Contributing ) + { + if ((e2Wc == 0 || e2Wc == 1) && + (m_ClipType != ctIntersection || + e2->polyType == ptSubject || (e2->windCnt2 != 0))) + DoEdge1(e1, e2, pt); + } + else if ( e2contributing ) + { + if ((e1Wc == 0 || e1Wc == 1) && + (m_ClipType != ctIntersection || + e1->polyType == ptSubject || (e1->windCnt2 != 0))) + DoEdge2(e1, e2, pt); + } + else if ( (e1Wc == 0 || e1Wc == 1) && + (e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops ) + { + //neither edge is currently contributing ... + + long64 e1Wc2, e2Wc2; + switch (e1FillType2) + { + case pftPositive: e1Wc2 = e1->windCnt2; break; + case pftNegative : e1Wc2 = -e1->windCnt2; break; + default: e1Wc2 = Abs(e1->windCnt2); + } + switch (e2FillType2) + { + case pftPositive: e2Wc2 = e2->windCnt2; break; + case pftNegative: e2Wc2 = -e2->windCnt2; break; + default: e2Wc2 = Abs(e2->windCnt2); + } + + if (e1->polyType != e2->polyType) + AddLocalMinPoly(e1, e2, pt); + else if (e1Wc == 1 && e2Wc == 1) + switch( m_ClipType ) { + case ctIntersection: + if (e1Wc2 > 0 && e2Wc2 > 0) + AddLocalMinPoly(e1, e2, pt); + break; + case ctUnion: + if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) + AddLocalMinPoly(e1, e2, pt); + break; + case ctDifference: + if (((e1->polyType == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((e1->polyType == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + AddLocalMinPoly(e1, e2, pt); + break; + case ctXor: + AddLocalMinPoly(e1, e2, pt); + } + else + SwapSides( *e1, *e2 ); + } + + if( (e1stops != e2stops) && + ( (e1stops && (e1->outIdx >= 0)) || (e2stops && (e2->outIdx >= 0)) ) ) + { + SwapSides( *e1, *e2 ); + SwapPolyIndexes( *e1, *e2 ); + } + + //finally, delete any non-contributing maxima edges ... + if( e1stops ) DeleteFromAEL( e1 ); + if( e2stops ) DeleteFromAEL( e2 ); +} +//------------------------------------------------------------------------------ + +void Clipper::SetHoleState(TEdge *e, OutRec *outRec) +{ + bool isHole = false; + TEdge *e2 = e->prevInAEL; + while (e2) + { + if (e2->outIdx >= 0) + { + isHole = !isHole; + if (! outRec->FirstLeft) + outRec->FirstLeft = m_PolyOuts[e2->outIdx]; + } + e2 = e2->prevInAEL; + } + if (isHole) outRec->isHole = true; +} +//------------------------------------------------------------------------------ + +OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) +{ + //work out which polygon fragment has the correct hole state ... + OutPt *outPt1 = outRec1->bottomPt; + OutPt *outPt2 = outRec2->bottomPt; + if (outPt1->pt.Y > outPt2->pt.Y) return outRec1; + else if (outPt1->pt.Y < outPt2->pt.Y) return outRec2; + else if (outPt1->pt.X < outPt2->pt.X) return outRec1; + else if (outPt1->pt.X > outPt2->pt.X) return outRec2; + else if (outPt1->next == outPt1) return outRec2; + else if (outPt2->next == outPt2) return outRec1; + else if (FirstIsBottomPt(outPt1, outPt2)) return outRec1; + else return outRec2; +} +//------------------------------------------------------------------------------ + +bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) +{ + do + { + outRec1 = outRec1->FirstLeft; + if (outRec1 == outRec2) return true; + } while (outRec1); + return false; +} +//------------------------------------------------------------------------------ + +void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) +{ + //get the start and ends of both output polygons ... + OutRec *outRec1 = m_PolyOuts[e1->outIdx]; + OutRec *outRec2 = m_PolyOuts[e2->outIdx]; + + OutRec *holeStateRec; + if (Param1RightOfParam2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + OutPt* p1_lft = outRec1->pts; + OutPt* p1_rt = p1_lft->prev; + OutPt* p2_lft = outRec2->pts; + OutPt* p2_rt = p2_lft->prev; + + EdgeSide side; + //join e2 poly onto e1 poly and delete pointers to e2 ... + if( e1->side == esLeft ) + { + if( e2->side == esLeft ) + { + //z y x a b c + ReversePolyPtLinks(p2_lft); + p2_lft->next = p1_lft; + p1_lft->prev = p2_lft; + p1_rt->next = p2_rt; + p2_rt->prev = p1_rt; + outRec1->pts = p2_rt; + } else + { + //x y z a b c + p2_rt->next = p1_lft; + p1_lft->prev = p2_rt; + p2_lft->prev = p1_rt; + p1_rt->next = p2_lft; + outRec1->pts = p2_lft; + } + side = esLeft; + } else + { + if( e2->side == esRight ) + { + //a b c z y x + ReversePolyPtLinks(p2_lft); + p1_rt->next = p2_rt; + p2_rt->prev = p1_rt; + p2_lft->next = p1_lft; + p1_lft->prev = p2_lft; + } else + { + //a b c x y z + p1_rt->next = p2_lft; + p2_lft->prev = p1_rt; + p1_lft->prev = p2_rt; + p2_rt->next = p1_lft; + } + side = esRight; + } + + if (holeStateRec == outRec2) + { + outRec1->bottomPt = outRec2->bottomPt; + outRec1->bottomPt->idx = outRec1->idx; + if (outRec2->FirstLeft != outRec1) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec1->isHole = outRec2->isHole; + } + outRec2->pts = 0; + outRec2->bottomPt = 0; + + outRec2->FirstLeft = outRec1; + + int OKIdx = e1->outIdx; + int ObsoleteIdx = e2->outIdx; + + e1->outIdx = -1; //nb: safe because we only get here via AddLocalMaxPoly + e2->outIdx = -1; + + TEdge* e = m_ActiveEdges; + while( e ) + { + if( e->outIdx == ObsoleteIdx ) + { + e->outIdx = OKIdx; + e->side = side; + break; + } + e = e->nextInAEL; + } + + for (JoinList::size_type i = 0; i < m_Joins.size(); ++i) + { + if (m_Joins[i]->poly1Idx == ObsoleteIdx) m_Joins[i]->poly1Idx = OKIdx; + if (m_Joins[i]->poly2Idx == ObsoleteIdx) m_Joins[i]->poly2Idx = OKIdx; + } + + for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) + { + if (m_HorizJoins[i]->savedIdx == ObsoleteIdx) + m_HorizJoins[i]->savedIdx = OKIdx; + } + +} +//------------------------------------------------------------------------------ + +OutRec* Clipper::CreateOutRec() +{ + OutRec* result = new OutRec; + result->isHole = false; + result->FirstLeft = 0; + result->pts = 0; + result->bottomPt = 0; + result->polyNode = 0; + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::AddOutPt(TEdge *e, const IntPoint &pt) +{ + bool ToFront = (e->side == esLeft); + if( e->outIdx < 0 ) + { + OutRec *outRec = CreateOutRec(); + m_PolyOuts.push_back(outRec); + outRec->idx = (int)m_PolyOuts.size()-1; + e->outIdx = outRec->idx; + OutPt* op = new OutPt; + outRec->pts = op; + outRec->bottomPt = op; + op->pt = pt; + op->idx = outRec->idx; + op->next = op; + op->prev = op; + SetHoleState(e, outRec); + } else + { + OutRec *outRec = m_PolyOuts[e->outIdx]; + OutPt* op = outRec->pts; + if ((ToFront && PointsEqual(pt, op->pt)) || + (!ToFront && PointsEqual(pt, op->prev->pt))) return; + + OutPt* op2 = new OutPt; + op2->pt = pt; + op2->idx = outRec->idx; + if (op2->pt.Y == outRec->bottomPt->pt.Y && + op2->pt.X < outRec->bottomPt->pt.X) + outRec->bottomPt = op2; + op2->next = op; + op2->prev = op->prev; + op2->prev->next = op2; + op->prev = op2; + if (ToFront) outRec->pts = op2; + } +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessHorizontals() +{ + TEdge* horzEdge = m_SortedEdges; + while( horzEdge ) + { + DeleteFromSEL( horzEdge ); + ProcessHorizontal( horzEdge ); + horzEdge = m_SortedEdges; + } +} +//------------------------------------------------------------------------------ + +bool Clipper::IsTopHorz(const long64 XPos) +{ + TEdge* e = m_SortedEdges; + while( e ) + { + if( ( XPos >= std::min(e->xcurr, e->xtop) ) && + ( XPos <= std::max(e->xcurr, e->xtop) ) ) return false; + e = e->nextInSEL; + } + return true; +} +//------------------------------------------------------------------------------ + +bool IsMinima(TEdge *e) +{ + return e && (e->prev->nextInLML != e) && (e->next->nextInLML != e); +} +//------------------------------------------------------------------------------ + +bool IsMaxima(TEdge *e, const long64 Y) +{ + return e && e->ytop == Y && !e->nextInLML; +} +//------------------------------------------------------------------------------ + +bool IsIntermediate(TEdge *e, const long64 Y) +{ + return e->ytop == Y && e->nextInLML; +} +//------------------------------------------------------------------------------ + +TEdge *GetMaximaPair(TEdge *e) +{ + if( !IsMaxima(e->next, e->ytop) || e->next->xtop != e->xtop ) + return e->prev; else + return e->next; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInAEL(TEdge *edge1, TEdge *edge2) +{ + if( edge1->nextInAEL == edge2 ) + { + TEdge* next = edge2->nextInAEL; + if( next ) next->prevInAEL = edge1; + TEdge* prev = edge1->prevInAEL; + if( prev ) prev->nextInAEL = edge2; + edge2->prevInAEL = prev; + edge2->nextInAEL = edge1; + edge1->prevInAEL = edge2; + edge1->nextInAEL = next; + } + else if( edge2->nextInAEL == edge1 ) + { + TEdge* next = edge1->nextInAEL; + if( next ) next->prevInAEL = edge2; + TEdge* prev = edge2->prevInAEL; + if( prev ) prev->nextInAEL = edge1; + edge1->prevInAEL = prev; + edge1->nextInAEL = edge2; + edge2->prevInAEL = edge1; + edge2->nextInAEL = next; + } + else + { + TEdge* next = edge1->nextInAEL; + TEdge* prev = edge1->prevInAEL; + edge1->nextInAEL = edge2->nextInAEL; + if( edge1->nextInAEL ) edge1->nextInAEL->prevInAEL = edge1; + edge1->prevInAEL = edge2->prevInAEL; + if( edge1->prevInAEL ) edge1->prevInAEL->nextInAEL = edge1; + edge2->nextInAEL = next; + if( edge2->nextInAEL ) edge2->nextInAEL->prevInAEL = edge2; + edge2->prevInAEL = prev; + if( edge2->prevInAEL ) edge2->prevInAEL->nextInAEL = edge2; + } + + if( !edge1->prevInAEL ) m_ActiveEdges = edge1; + else if( !edge2->prevInAEL ) m_ActiveEdges = edge2; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInSEL(TEdge *edge1, TEdge *edge2) +{ + if( !( edge1->nextInSEL ) && !( edge1->prevInSEL ) ) return; + if( !( edge2->nextInSEL ) && !( edge2->prevInSEL ) ) return; + + if( edge1->nextInSEL == edge2 ) + { + TEdge* next = edge2->nextInSEL; + if( next ) next->prevInSEL = edge1; + TEdge* prev = edge1->prevInSEL; + if( prev ) prev->nextInSEL = edge2; + edge2->prevInSEL = prev; + edge2->nextInSEL = edge1; + edge1->prevInSEL = edge2; + edge1->nextInSEL = next; + } + else if( edge2->nextInSEL == edge1 ) + { + TEdge* next = edge1->nextInSEL; + if( next ) next->prevInSEL = edge2; + TEdge* prev = edge2->prevInSEL; + if( prev ) prev->nextInSEL = edge1; + edge1->prevInSEL = prev; + edge1->nextInSEL = edge2; + edge2->prevInSEL = edge1; + edge2->nextInSEL = next; + } + else + { + TEdge* next = edge1->nextInSEL; + TEdge* prev = edge1->prevInSEL; + edge1->nextInSEL = edge2->nextInSEL; + if( edge1->nextInSEL ) edge1->nextInSEL->prevInSEL = edge1; + edge1->prevInSEL = edge2->prevInSEL; + if( edge1->prevInSEL ) edge1->prevInSEL->nextInSEL = edge1; + edge2->nextInSEL = next; + if( edge2->nextInSEL ) edge2->nextInSEL->prevInSEL = edge2; + edge2->prevInSEL = prev; + if( edge2->prevInSEL ) edge2->prevInSEL->nextInSEL = edge2; + } + + if( !edge1->prevInSEL ) m_SortedEdges = edge1; + else if( !edge2->prevInSEL ) m_SortedEdges = edge2; +} +//------------------------------------------------------------------------------ + +TEdge* GetNextInAEL(TEdge *e, Direction dir) +{ + return dir == dLeftToRight ? e->nextInAEL : e->prevInAEL; +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessHorizontal(TEdge *horzEdge) +{ + Direction dir; + long64 horzLeft, horzRight; + + if( horzEdge->xcurr < horzEdge->xtop ) + { + horzLeft = horzEdge->xcurr; + horzRight = horzEdge->xtop; + dir = dLeftToRight; + } else + { + horzLeft = horzEdge->xtop; + horzRight = horzEdge->xcurr; + dir = dRightToLeft; + } + + TEdge* eMaxPair; + if( horzEdge->nextInLML ) eMaxPair = 0; + else eMaxPair = GetMaximaPair(horzEdge); + + TEdge* e = GetNextInAEL( horzEdge , dir ); + while( e ) + { + TEdge* eNext = GetNextInAEL( e, dir ); + + if (eMaxPair || + ((dir == dLeftToRight) && (e->xcurr <= horzRight)) || + ((dir == dRightToLeft) && (e->xcurr >= horzLeft))) + { + //ok, so far it looks like we're still in range of the horizontal edge + if ( e->xcurr == horzEdge->xtop && !eMaxPair ) + { + if (SlopesEqual(*e, *horzEdge->nextInLML, m_UseFullRange)) + { + //if output polygons share an edge, they'll need joining later ... + if (horzEdge->outIdx >= 0 && e->outIdx >= 0) + AddJoin(horzEdge->nextInLML, e, horzEdge->outIdx); + break; //we've reached the end of the horizontal line + } + else if (e->dx < horzEdge->nextInLML->dx) + //we really have got to the end of the intermediate horz edge so quit. + //nb: More -ve slopes follow more +ve slopes ABOVE the horizontal. + break; + } + + if( e == eMaxPair ) + { + //horzEdge is evidently a maxima horizontal and we've arrived at its end. + if (dir == dLeftToRight) + IntersectEdges(horzEdge, e, IntPoint(e->xcurr, horzEdge->ycurr), ipNone); + else + IntersectEdges(e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), ipNone); + if (eMaxPair->outIdx >= 0) throw clipperException("ProcessHorizontal error"); + return; + } + else if( NEAR_EQUAL(e->dx, HORIZONTAL) && !IsMinima(e) && !(e->xcurr > e->xtop) ) + { + //An overlapping horizontal edge. Overlapping horizontal edges are + //processed as if layered with the current horizontal edge (horizEdge) + //being infinitesimally lower that the next (e). Therfore, we + //intersect with e only if e.xcurr is within the bounds of horzEdge ... + if( dir == dLeftToRight ) + IntersectEdges( horzEdge , e, IntPoint(e->xcurr, horzEdge->ycurr), + (IsTopHorz( e->xcurr ))? ipLeft : ipBoth ); + else + IntersectEdges( e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), + (IsTopHorz( e->xcurr ))? ipRight : ipBoth ); + } + else if( dir == dLeftToRight ) + { + IntersectEdges( horzEdge, e, IntPoint(e->xcurr, horzEdge->ycurr), + (IsTopHorz( e->xcurr ))? ipLeft : ipBoth ); + } + else + { + IntersectEdges( e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), + (IsTopHorz( e->xcurr ))? ipRight : ipBoth ); + } + SwapPositionsInAEL( horzEdge, e ); + } + else if( (dir == dLeftToRight && e->xcurr > horzRight && m_SortedEdges) || + (dir == dRightToLeft && e->xcurr < horzLeft && m_SortedEdges) ) break; + e = eNext; + } //end while + + if( horzEdge->nextInLML ) + { + if( horzEdge->outIdx >= 0 ) + AddOutPt( horzEdge, IntPoint(horzEdge->xtop, horzEdge->ytop)); + UpdateEdgeIntoAEL( horzEdge ); + } + else + { + if ( horzEdge->outIdx >= 0 ) + IntersectEdges( horzEdge, eMaxPair, + IntPoint(horzEdge->xtop, horzEdge->ycurr), ipBoth); + if (eMaxPair->outIdx >= 0) throw clipperException("ProcessHorizontal error"); + DeleteFromAEL(eMaxPair); + DeleteFromAEL(horzEdge); + } +} +//------------------------------------------------------------------------------ + +void Clipper::UpdateEdgeIntoAEL(TEdge *&e) +{ + if( !e->nextInLML ) throw + clipperException("UpdateEdgeIntoAEL: invalid call"); + TEdge* AelPrev = e->prevInAEL; + TEdge* AelNext = e->nextInAEL; + e->nextInLML->outIdx = e->outIdx; + if( AelPrev ) AelPrev->nextInAEL = e->nextInLML; + else m_ActiveEdges = e->nextInLML; + if( AelNext ) AelNext->prevInAEL = e->nextInLML; + e->nextInLML->side = e->side; + e->nextInLML->windDelta = e->windDelta; + e->nextInLML->windCnt = e->windCnt; + e->nextInLML->windCnt2 = e->windCnt2; + e = e->nextInLML; + e->prevInAEL = AelPrev; + e->nextInAEL = AelNext; + if( !NEAR_EQUAL(e->dx, HORIZONTAL) ) InsertScanbeam( e->ytop ); +} +//------------------------------------------------------------------------------ + +bool Clipper::ProcessIntersections(const long64 botY, const long64 topY) +{ + if( !m_ActiveEdges ) return true; + try { + BuildIntersectList(botY, topY); + if ( !m_IntersectNodes) return true; + if ( FixupIntersections() ) ProcessIntersectList(); + else return false; + } + catch(...) { + m_SortedEdges = 0; + DisposeIntersectNodes(); + throw clipperException("ProcessIntersections error"); + } + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeIntersectNodes() +{ + while ( m_IntersectNodes ) + { + IntersectNode* iNode = m_IntersectNodes->next; + delete m_IntersectNodes; + m_IntersectNodes = iNode; + } +} +//------------------------------------------------------------------------------ + +void Clipper::BuildIntersectList(const long64 botY, const long64 topY) +{ + if ( !m_ActiveEdges ) return; + + //prepare for sorting ... + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while( e ) + { + e->prevInSEL = e->prevInAEL; + e->nextInSEL = e->nextInAEL; + e->tmpX = TopX( *e, topY ); + e = e->nextInAEL; + } + + //bubblesort ... + bool isModified = true; + while( isModified && m_SortedEdges ) + { + isModified = false; + e = m_SortedEdges; + while( e->nextInSEL ) + { + TEdge *eNext = e->nextInSEL; + IntPoint pt; + if(e->tmpX > eNext->tmpX && + IntersectPoint(*e, *eNext, pt, m_UseFullRange)) + { + if (pt.Y > botY) + { + pt.Y = botY; + pt.X = TopX(*e, pt.Y); + } + AddIntersectNode( e, eNext, pt ); + SwapPositionsInSEL(e, eNext); + isModified = true; + } + else + e = eNext; + } + if( e->prevInSEL ) e->prevInSEL->nextInSEL = 0; + else break; + } + m_SortedEdges = 0; +} +//------------------------------------------------------------------------------ + +bool ProcessParam1BeforeParam2(const IntersectNode &node1, const IntersectNode &node2) +{ + bool result; + if (node1.pt.Y == node2.pt.Y) + { + if (node1.edge1 == node2.edge1 || node1.edge2 == node2.edge1) + { + result = node2.pt.X > node1.pt.X; + return node2.edge1->dx > 0 ? !result : result; + } + else if (node1.edge1 == node2.edge2 || node1.edge2 == node2.edge2) + { + result = node2.pt.X > node1.pt.X; + return node2.edge2->dx > 0 ? !result : result; + } + else return node2.pt.X > node1.pt.X; + } + else return node1.pt.Y > node2.pt.Y; +} +//------------------------------------------------------------------------------ + +void Clipper::AddIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt) +{ + IntersectNode* newNode = new IntersectNode; + newNode->edge1 = e1; + newNode->edge2 = e2; + newNode->pt = pt; + newNode->next = 0; + if( !m_IntersectNodes ) m_IntersectNodes = newNode; + else if( ProcessParam1BeforeParam2(*newNode, *m_IntersectNodes) ) + { + newNode->next = m_IntersectNodes; + m_IntersectNodes = newNode; + } + else + { + IntersectNode* iNode = m_IntersectNodes; + while( iNode->next && ProcessParam1BeforeParam2(*iNode->next, *newNode) ) + iNode = iNode->next; + newNode->next = iNode->next; + iNode->next = newNode; + } +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessIntersectList() +{ + while( m_IntersectNodes ) + { + IntersectNode* iNode = m_IntersectNodes->next; + { + IntersectEdges( m_IntersectNodes->edge1 , + m_IntersectNodes->edge2 , m_IntersectNodes->pt, ipBoth ); + SwapPositionsInAEL( m_IntersectNodes->edge1 , m_IntersectNodes->edge2 ); + } + delete m_IntersectNodes; + m_IntersectNodes = iNode; + } +} +//------------------------------------------------------------------------------ + +void Clipper::DoMaxima(TEdge *e, long64 topY) +{ + TEdge* eMaxPair = GetMaximaPair(e); + long64 X = e->xtop; + TEdge* eNext = e->nextInAEL; + while( eNext != eMaxPair ) + { + if (!eNext) throw clipperException("DoMaxima error"); + IntersectEdges( e, eNext, IntPoint(X, topY), ipBoth ); + SwapPositionsInAEL(e, eNext); + eNext = eNext->nextInAEL; + } + if( e->outIdx < 0 && eMaxPair->outIdx < 0 ) + { + DeleteFromAEL( e ); + DeleteFromAEL( eMaxPair ); + } + else if( e->outIdx >= 0 && eMaxPair->outIdx >= 0 ) + { + IntersectEdges( e, eMaxPair, IntPoint(X, topY), ipNone ); + } + else throw clipperException("DoMaxima error"); +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessEdgesAtTopOfScanbeam(const long64 topY) +{ + TEdge* e = m_ActiveEdges; + while( e ) + { + //1. process maxima, treating them as if they're 'bent' horizontal edges, + // but exclude maxima with horizontal edges. nb: e can't be a horizontal. + if( IsMaxima(e, topY) && !NEAR_EQUAL(GetMaximaPair(e)->dx, HORIZONTAL) ) + { + //'e' might be removed from AEL, as may any following edges so ... + TEdge* ePrev = e->prevInAEL; + DoMaxima(e, topY); + if( !ePrev ) e = m_ActiveEdges; + else e = ePrev->nextInAEL; + } + else + { + //2. promote horizontal edges, otherwise update xcurr and ycurr ... + if( IsIntermediate(e, topY) && NEAR_EQUAL(e->nextInLML->dx, HORIZONTAL) ) + { + if (e->outIdx >= 0) + { + AddOutPt(e, IntPoint(e->xtop, e->ytop)); + + for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) + { + IntPoint pt, pt2; + HorzJoinRec* hj = m_HorizJoins[i]; + if (GetOverlapSegment(IntPoint(hj->edge->xbot, hj->edge->ybot), + IntPoint(hj->edge->xtop, hj->edge->ytop), + IntPoint(e->nextInLML->xbot, e->nextInLML->ybot), + IntPoint(e->nextInLML->xtop, e->nextInLML->ytop), pt, pt2)) + AddJoin(hj->edge, e->nextInLML, hj->savedIdx, e->outIdx); + } + + AddHorzJoin(e->nextInLML, e->outIdx); + } + UpdateEdgeIntoAEL(e); + AddEdgeToSEL(e); + } else + { + //this just simplifies horizontal processing ... + e->xcurr = TopX( *e, topY ); + e->ycurr = topY; + } + e = e->nextInAEL; + } + } + + //3. Process horizontals at the top of the scanbeam ... + ProcessHorizontals(); + + //4. Promote intermediate vertices ... + e = m_ActiveEdges; + while( e ) + { + if( IsIntermediate( e, topY ) ) + { + if( e->outIdx >= 0 ) AddOutPt(e, IntPoint(e->xtop,e->ytop)); + UpdateEdgeIntoAEL(e); + + //if output polygons share an edge, they'll need joining later ... + TEdge* ePrev = e->prevInAEL; + TEdge* eNext = e->nextInAEL; + if (ePrev && ePrev->xcurr == e->xbot && + ePrev->ycurr == e->ybot && e->outIdx >= 0 && + ePrev->outIdx >= 0 && ePrev->ycurr > ePrev->ytop && + SlopesEqual(*e, *ePrev, m_UseFullRange)) + { + AddOutPt(ePrev, IntPoint(e->xbot, e->ybot)); + AddJoin(e, ePrev); + } + else if (eNext && eNext->xcurr == e->xbot && + eNext->ycurr == e->ybot && e->outIdx >= 0 && + eNext->outIdx >= 0 && eNext->ycurr > eNext->ytop && + SlopesEqual(*e, *eNext, m_UseFullRange)) + { + AddOutPt(eNext, IntPoint(e->xbot, e->ybot)); + AddJoin(e, eNext); + } + } + e = e->nextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::FixupOutPolygon(OutRec &outRec) +{ + //FixupOutPolygon() - removes duplicate points and simplifies consecutive + //parallel edges by removing the middle vertex. + OutPt *lastOK = 0; + outRec.pts = outRec.bottomPt; + OutPt *pp = outRec.bottomPt; + + for (;;) + { + if (pp->prev == pp || pp->prev == pp->next ) + { + DisposeOutPts(pp); + outRec.pts = 0; + outRec.bottomPt = 0; + return; + } + //test for duplicate points and for same slope (cross-product) ... + if ( PointsEqual(pp->pt, pp->next->pt) || + SlopesEqual(pp->prev->pt, pp->pt, pp->next->pt, m_UseFullRange) ) + { + lastOK = 0; + OutPt *tmp = pp; + if (pp == outRec.bottomPt) + outRec.bottomPt = 0; //flags need for updating + pp->prev->next = pp->next; + pp->next->prev = pp->prev; + pp = pp->prev; + delete tmp; + } + else if (pp == lastOK) break; + else + { + if (!lastOK) lastOK = pp; + pp = pp->next; + } + } + if (!outRec.bottomPt) { + outRec.bottomPt = GetBottomPt(pp); + outRec.bottomPt->idx = outRec.idx; + outRec.pts = outRec.bottomPt; + } +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult(Polygons &polys) +{ + int k = 0; + polys.resize(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + if (m_PolyOuts[i]->pts) + { + Polygon* pg = &polys[k]; + pg->clear(); + OutPt* p = m_PolyOuts[i]->pts; + do + { + pg->push_back(p->pt); + p = p->prev; + } while (p != m_PolyOuts[i]->pts); + //make sure each polygon has at least 3 vertices ... + if (pg->size() < 3) pg->clear(); else k++; + } + } + polys.resize(k); +} +//------------------------------------------------------------------------------ + +int PointCount(OutPt *pts) +{ + if (!pts) return 0; + int result = 0; + OutPt* p = pts; + do + { + result++; + p = p->next; + } + while (p != pts); + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult2(PolyTree& polytree) +{ + polytree.Clear(); + polytree.AllNodes.reserve(m_PolyOuts.size()); + //add each output polygon/contour to polytree ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec->pts); + if (cnt < 3) continue; + FixHoleLinkage(*outRec); + PolyNode* pn = new PolyNode(); + //nb: polytree takes ownership of all the PolyNodes + polytree.AllNodes.push_back(pn); + outRec->polyNode = pn; + pn->Parent = 0; + pn->Index = 0; + pn->Contour.reserve(cnt); + OutPt *op = outRec->pts; + for (int j = 0; j < cnt; j++) + { + pn->Contour.push_back(op->pt); + op = op->prev; + } + } + + //fixup PolyNode links etc ... + polytree.Childs.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + if (!outRec->polyNode) continue; + if (outRec->FirstLeft) + outRec->FirstLeft->polyNode->AddChild(*outRec->polyNode); + else + polytree.AddChild(*outRec->polyNode); + } +} +//------------------------------------------------------------------------------ + +void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) +{ + TEdge *e1 = int1.edge1; + TEdge *e2 = int1.edge2; + IntPoint p = int1.pt; + + int1.edge1 = int2.edge1; + int1.edge2 = int2.edge2; + int1.pt = int2.pt; + + int2.edge1 = e1; + int2.edge2 = e2; + int2.pt = p; +} +//------------------------------------------------------------------------------ + +bool Clipper::FixupIntersections() +{ + if ( !m_IntersectNodes->next ) return true; + + CopyAELToSEL(); + IntersectNode *int1 = m_IntersectNodes; + IntersectNode *int2 = m_IntersectNodes->next; + while (int2) + { + TEdge *e1 = int1->edge1; + TEdge *e2; + if (e1->prevInSEL == int1->edge2) e2 = e1->prevInSEL; + else if (e1->nextInSEL == int1->edge2) e2 = e1->nextInSEL; + else + { + //The current intersection is out of order, so try and swap it with + //a subsequent intersection ... + while (int2) + { + if (int2->edge1->nextInSEL == int2->edge2 || + int2->edge1->prevInSEL == int2->edge2) break; + else int2 = int2->next; + } + if ( !int2 ) return false; //oops!!! + + //found an intersect node that can be swapped ... + SwapIntersectNodes(*int1, *int2); + e1 = int1->edge1; + e2 = int1->edge2; + } + SwapPositionsInSEL(e1, e2); + int1 = int1->next; + int2 = int1->next; + } + + m_SortedEdges = 0; + + //finally, check the last intersection too ... + return (int1->edge1->prevInSEL == int1->edge2 || + int1->edge1->nextInSEL == int1->edge2); +} +//------------------------------------------------------------------------------ + +bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) +{ + return e2.xcurr == e1.xcurr ? e2.dx > e1.dx : e2.xcurr < e1.xcurr; +} +//------------------------------------------------------------------------------ + +void Clipper::InsertEdgeIntoAEL(TEdge *edge) +{ + edge->prevInAEL = 0; + edge->nextInAEL = 0; + if( !m_ActiveEdges ) + { + m_ActiveEdges = edge; + } + else if( E2InsertsBeforeE1(*m_ActiveEdges, *edge) ) + { + edge->nextInAEL = m_ActiveEdges; + m_ActiveEdges->prevInAEL = edge; + m_ActiveEdges = edge; + } else + { + TEdge* e = m_ActiveEdges; + while( e->nextInAEL && !E2InsertsBeforeE1(*e->nextInAEL , *edge) ) + e = e->nextInAEL; + edge->nextInAEL = e->nextInAEL; + if( e->nextInAEL ) e->nextInAEL->prevInAEL = edge; + edge->prevInAEL = e; + e->nextInAEL = edge; + } +} +//---------------------------------------------------------------------- + +void Clipper::DoEdge1(TEdge *edge1, TEdge *edge2, const IntPoint &pt) +{ + AddOutPt(edge1, pt); + SwapSides(*edge1, *edge2); + SwapPolyIndexes(*edge1, *edge2); +} +//---------------------------------------------------------------------- + +void Clipper::DoEdge2(TEdge *edge1, TEdge *edge2, const IntPoint &pt) +{ + AddOutPt(edge2, pt); + SwapSides(*edge1, *edge2); + SwapPolyIndexes(*edge1, *edge2); +} +//---------------------------------------------------------------------- + +void Clipper::DoBothEdges(TEdge *edge1, TEdge *edge2, const IntPoint &pt) +{ + AddOutPt(edge1, pt); + AddOutPt(edge2, pt); + SwapSides( *edge1 , *edge2 ); + SwapPolyIndexes( *edge1 , *edge2 ); +} +//---------------------------------------------------------------------- + +bool Clipper::JoinPoints(const JoinRec *j, OutPt *&p1, OutPt *&p2) +{ + OutRec *outRec1 = m_PolyOuts[j->poly1Idx]; + OutRec *outRec2 = m_PolyOuts[j->poly2Idx]; + if (!outRec1 || !outRec2) return false; + OutPt *pp1a = outRec1->pts; + OutPt *pp2a = outRec2->pts; + IntPoint pt1 = j->pt2a, pt2 = j->pt2b; + IntPoint pt3 = j->pt1a, pt4 = j->pt1b; + if (!FindSegment(pp1a, pt1, pt2)) return false; + if (outRec1 == outRec2) + { + //we're searching the same polygon for overlapping segments so + //segment 2 mustn't be the same as segment 1 ... + pp2a = pp1a->next; + if (!FindSegment(pp2a, pt3, pt4) || (pp2a == pp1a)) return false; + } + else if (!FindSegment(pp2a, pt3, pt4)) return false; + + if (!GetOverlapSegment(pt1, pt2, pt3, pt4, pt1, pt2)) return false; + + OutPt *p3, *p4, *prev = pp1a->prev; + //get p1 & p2 polypts - the overlap start & endpoints on poly1 + if (PointsEqual(pp1a->pt, pt1)) p1 = pp1a; + else if (PointsEqual(prev->pt, pt1)) p1 = prev; + else p1 = InsertPolyPtBetween(pp1a, prev, pt1); + + if (PointsEqual(pp1a->pt, pt2)) p2 = pp1a; + else if (PointsEqual(prev->pt, pt2)) p2 = prev; + else if ((p1 == pp1a) || (p1 == prev)) + p2 = InsertPolyPtBetween(pp1a, prev, pt2); + else if (Pt3IsBetweenPt1AndPt2(pp1a->pt, p1->pt, pt2)) + p2 = InsertPolyPtBetween(pp1a, p1, pt2); else + p2 = InsertPolyPtBetween(p1, prev, pt2); + + //get p3 & p4 polypts - the overlap start & endpoints on poly2 + prev = pp2a->prev; + if (PointsEqual(pp2a->pt, pt1)) p3 = pp2a; + else if (PointsEqual(prev->pt, pt1)) p3 = prev; + else p3 = InsertPolyPtBetween(pp2a, prev, pt1); + + if (PointsEqual(pp2a->pt, pt2)) p4 = pp2a; + else if (PointsEqual(prev->pt, pt2)) p4 = prev; + else if ((p3 == pp2a) || (p3 == prev)) + p4 = InsertPolyPtBetween(pp2a, prev, pt2); + else if (Pt3IsBetweenPt1AndPt2(pp2a->pt, p3->pt, pt2)) + p4 = InsertPolyPtBetween(pp2a, p3, pt2); else + p4 = InsertPolyPtBetween(p3, prev, pt2); + + //p1.pt == p3.pt and p2.pt == p4.pt so join p1 to p3 and p2 to p4 ... + if (p1->next == p2 && p3->prev == p4) + { + p1->next = p3; + p3->prev = p1; + p2->prev = p4; + p4->next = p2; + return true; + } + else if (p1->prev == p2 && p3->next == p4) + { + p1->prev = p3; + p3->next = p1; + p2->next = p4; + p4->prev = p2; + return true; + } + else + return false; //an orientation is probably wrong +} +//---------------------------------------------------------------------- + +void Clipper::FixupJoinRecs(JoinRec *j, OutPt *pt, unsigned startIdx) +{ + for (JoinList::size_type k = startIdx; k < m_Joins.size(); k++) + { + JoinRec* j2 = m_Joins[k]; + if (j2->poly1Idx == j->poly1Idx && PointIsVertex(j2->pt1a, pt)) + j2->poly1Idx = j->poly2Idx; + if (j2->poly2Idx == j->poly1Idx && PointIsVertex(j2->pt2a, pt)) + j2->poly2Idx = j->poly2Idx; + } +} +//---------------------------------------------------------------------- + +bool Poly2ContainsPoly1(OutPt* outPt1, OutPt* outPt2, bool UseFullInt64Range) +{ + //find the first pt in outPt1 that isn't also a vertex of outPt2 ... + OutPt* outPt = outPt1; + do + { + if (!PointIsVertex(outPt->pt, outPt2)) break; + outPt = outPt->next; + } + while (outPt != outPt1); + bool result; + //sometimes a point on one polygon can be touching the other polygon + //so to be totally confident outPt1 is inside outPt2 repeat ... + do + { + result = PointInPolygon(outPt->pt, outPt2, UseFullInt64Range); + outPt = outPt->next; + } + while (result && outPt != outPt1); + return result; +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) +{ + + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + if (outRec->pts && outRec->FirstLeft == OldOutRec) + { + if (Poly2ContainsPoly1(outRec->pts, NewOutRec->pts, m_UseFullRange)) + outRec->FirstLeft = NewOutRec; + } + } +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) +{ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec; + } +} +//---------------------------------------------------------------------- + +void Clipper::JoinCommonEdges() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + { + JoinRec* j = m_Joins[i]; + + OutRec *outRec1 = m_PolyOuts[j->poly1Idx]; + OutRec *outRec2 = m_PolyOuts[j->poly2Idx]; + + if (!outRec1->pts || !outRec2->pts) continue; + + //get the polygon fragment with the correct hole state (FirstLeft) + //before calling JoinPoints() ... + OutRec *holeStateRec; + if (outRec1 == outRec2) holeStateRec = outRec1; + else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; + else holeStateRec = GetLowermostRec(outRec1, outRec2); + + OutPt *p1, *p2; + if (!JoinPoints(j, p1, p2)) continue; + + if (outRec1 == outRec2) + { + //instead of joining two polygons, we've just created a new one by + //splitting one polygon into two. + outRec1->pts = GetBottomPt(p1); + outRec1->bottomPt = outRec1->pts; + outRec1->bottomPt->idx = outRec1->idx; + outRec2 = CreateOutRec(); + m_PolyOuts.push_back(outRec2); + outRec2->idx = (int)m_PolyOuts.size()-1; + j->poly2Idx = outRec2->idx; + outRec2->pts = GetBottomPt(p2); + outRec2->bottomPt = outRec2->pts; + outRec2->bottomPt->idx = outRec2->idx; + + if (Poly2ContainsPoly1(outRec2->pts, outRec1->pts, m_UseFullRange)) + { + //outRec2 is contained by outRec1 ... + outRec2->isHole = !outRec1->isHole; + outRec2->FirstLeft = outRec1; + + FixupJoinRecs(j, p2, i+1); + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + + FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() + + + if ((outRec2->isHole ^ m_ReverseOutput) == (Area(*outRec2, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec2->pts); + + } else if (Poly2ContainsPoly1(outRec1->pts, outRec2->pts, m_UseFullRange)) + { + //outRec1 is contained by outRec2 ... + outRec2->isHole = outRec1->isHole; + outRec1->isHole = !outRec2->isHole; + outRec2->FirstLeft = outRec1->FirstLeft; + outRec1->FirstLeft = outRec2; + + FixupJoinRecs(j, p2, i+1); + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); + + FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() + + if ((outRec1->isHole ^ m_ReverseOutput) == (Area(*outRec1, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec1->pts); + } + else + { + //the 2 polygons are completely separate ... + outRec2->isHole = outRec1->isHole; + outRec2->FirstLeft = outRec1->FirstLeft; + + FixupJoinRecs(j, p2, i+1); + + //fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); + + FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() + } + + } else + { + //joined 2 polygons together ... + + //cleanup redundant edges ... + FixupOutPolygon(*outRec1); + + //delete the obsolete pointer ... + int OKIdx = outRec1->idx; + int ObsoleteIdx = outRec2->idx; + outRec2->pts = 0; + outRec2->bottomPt = 0; + + outRec1->isHole = holeStateRec->isHole; + if (holeStateRec == outRec2) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec2->FirstLeft = outRec1; + + //now fixup any subsequent Joins that match this polygon + for (JoinList::size_type k = i+1; k < m_Joins.size(); k++) + { + JoinRec* j2 = m_Joins[k]; + if (j2->poly1Idx == ObsoleteIdx) j2->poly1Idx = OKIdx; + if (j2->poly2Idx == ObsoleteIdx) j2->poly2Idx = OKIdx; + } + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + } + } +} +//------------------------------------------------------------------------------ + +void ReversePolygon(Polygon& p) +{ + std::reverse(p.begin(), p.end()); +} +//------------------------------------------------------------------------------ + +void ReversePolygons(Polygons& p) +{ + for (Polygons::size_type i = 0; i < p.size(); ++i) + ReversePolygon(p[i]); +} + +//------------------------------------------------------------------------------ +// OffsetPolygon functions ... +//------------------------------------------------------------------------------ + +struct DoublePoint +{ + double X; + double Y; + DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} +}; +//------------------------------------------------------------------------------ + +Polygon BuildArc(const IntPoint &pt, + const double a1, const double a2, const double r) +{ + long64 steps = std::max(6, int(std::sqrt(std::fabs(r)) * std::fabs(a2 - a1))); + if (steps > 0x100) steps = 0x100; + int n = (unsigned)steps; + Polygon result(n); + double da = (a2 - a1) / (n -1); + double a = a1; + for (int i = 0; i < n; ++i) + { + result[i].X = pt.X + Round(std::cos(a)*r); + result[i].Y = pt.Y + Round(std::sin(a)*r); + a += da; + } + return result; +} +//------------------------------------------------------------------------------ + +DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) +{ + if(pt2.X == pt1.X && pt2.Y == pt1.Y) + return DoublePoint(0, 0); + + double dx = (double)(pt2.X - pt1.X); + double dy = (double)(pt2.Y - pt1.Y); + double f = 1 *1.0/ std::sqrt( dx*dx + dy*dy ); + dx *= f; + dy *= f; + return DoublePoint(dy, -dx); +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +class PolyOffsetBuilder +{ +private: + Polygons m_p; + Polygon* m_curr_poly; + std::vector normals; + double m_delta, m_RMin, m_R; + size_t m_i, m_j, m_k; + static const int buffLength = 128; + JoinType m_jointype; + +public: + +PolyOffsetBuilder(const Polygons& in_polys, Polygons& out_polys, + double delta, JoinType jointype, double MiterLimit, bool AutoFix) +{ + //nb precondition - out_polys != ptsin_polys + if (NEAR_ZERO(delta)) + { + out_polys = in_polys; + return; + } + + this->m_p = in_polys; + this->m_delta = delta; + this->m_jointype = jointype; + + //ChecksInput - fixes polygon orientation if necessary and removes + //duplicate vertices. Can be set false when you're sure that polygon + //orientation is correct and that there are no duplicate vertices. + if (AutoFix) + { + size_t Len = m_p.size(), botI = 0; + while (botI < Len && m_p[botI].size() == 0) botI++; + if (botI == Len) return; + + //botPt: used to find the lowermost (in inverted Y-axis) & leftmost point + //This point (on m_p[botI]) must be on an outer polygon ring and if + //its orientation is false (counterclockwise) then assume all polygons + //need reversing ... + IntPoint botPt = m_p[botI][0]; + for (size_t i = botI; i < Len; ++i) + { + if (m_p[i].size() < 3) continue; + if (UpdateBotPt(m_p[i][0], botPt)) botI = i; + Polygon::iterator it = m_p[i].begin() +1; + while (it != m_p[i].end()) + { + if (PointsEqual(*it, *(it -1))) + it = m_p[i].erase(it); + else + { + if (UpdateBotPt(*it, botPt)) botI = i; + ++it; + } + } + } + if (!Orientation(m_p[botI])) + ReversePolygons(m_p); + } + + if (MiterLimit <= 1) MiterLimit = 1; + m_RMin = 2.0/(MiterLimit*MiterLimit); + + double deltaSq = delta*delta; + out_polys.clear(); + out_polys.resize(m_p.size()); + for (m_i = 0; m_i < m_p.size(); m_i++) + { + m_curr_poly = &out_polys[m_i]; + size_t len = m_p[m_i].size(); + if (len > 1 && m_p[m_i][0].X == m_p[m_i][len - 1].X && + m_p[m_i][0].Y == m_p[m_i][len-1].Y) len--; + + //when 'shrinking' polygons - to minimize artefacts + //strip those polygons that have an area < pi * delta^2 ... + double a1 = Area(m_p[m_i]); + if (delta < 0) { if (a1 > 0 && a1 < deltaSq *pi) len = 0; } + else if (a1 < 0 && -a1 < deltaSq *pi) len = 0; //holes have neg. area + + if (len == 0 || (len < 3 && delta <= 0)) + continue; + else if (len == 1) + { + Polygon arc; + arc = BuildArc(m_p[m_i][len-1], 0, 2 * pi, delta); + out_polys[m_i] = arc; + continue; + } + + //build normals ... + normals.clear(); + normals.resize(len); + normals[len-1] = GetUnitNormal(m_p[m_i][len-1], m_p[m_i][0]); + for (m_j = 0; m_j < len -1; ++m_j) + normals[m_j] = GetUnitNormal(m_p[m_i][m_j], m_p[m_i][m_j+1]); + + m_k = len -1; + for (m_j = 0; m_j < len; ++m_j) + { + switch (jointype) + { + case jtMiter: + { + m_R = 1 + (normals[m_j].X*normals[m_k].X + + normals[m_j].Y*normals[m_k].Y); + if (m_R >= m_RMin) DoMiter(); else DoSquare(MiterLimit); + break; + } + case jtSquare: DoSquare(); break; + case jtRound: DoRound(); break; + } + m_k = m_j; + } + } + + //finally, clean up untidy corners using Clipper ... + Clipper clpr; + clpr.AddPolygons(out_polys, ptSubject); + if (delta > 0) + { + if (!clpr.Execute(ctUnion, out_polys, pftPositive, pftPositive)) + out_polys.clear(); + } + else + { + IntRect r = clpr.GetBounds(); + Polygon outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPolygon(outer, ptSubject); + if (clpr.Execute(ctUnion, out_polys, pftNegative, pftNegative)) + { + out_polys.erase(out_polys.begin()); + ReversePolygons(out_polys); + + } else + out_polys.clear(); + } +} +//------------------------------------------------------------------------------ + +private: + +void AddPoint(const IntPoint& pt) +{ + Polygon::size_type len = m_curr_poly->size(); + if (len == m_curr_poly->capacity()) + m_curr_poly->reserve(len + buffLength); + m_curr_poly->push_back(pt); +} +//------------------------------------------------------------------------------ + +void DoSquare(double mul = 1.0) +{ + IntPoint pt1 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), + (long64)Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); + IntPoint pt2 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), + (long64)Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); + if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * m_delta >= 0) + { + double a1 = std::atan2(normals[m_k].Y, normals[m_k].X); + double a2 = std::atan2(-normals[m_j].Y, -normals[m_j].X); + a1 = std::fabs(a2 - a1); + if (a1 > pi) a1 = pi * 2 - a1; + double dx = std::tan((pi - a1) / 4) * std::fabs(m_delta * mul); + pt1 = IntPoint((long64)(pt1.X -normals[m_k].Y * dx), + (long64)(pt1.Y + normals[m_k].X * dx)); + AddPoint(pt1); + pt2 = IntPoint((long64)(pt2.X + normals[m_j].Y * dx), + (long64)(pt2.Y -normals[m_j].X * dx)); + AddPoint(pt2); + } + else + { + AddPoint(pt1); + AddPoint(m_p[m_i][m_j]); + AddPoint(pt2); + } +} +//------------------------------------------------------------------------------ + +void DoMiter() +{ + if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * m_delta >= 0) + { + double q = m_delta / m_R; + AddPoint(IntPoint((long64)Round(m_p[m_i][m_j].X + + (normals[m_k].X + normals[m_j].X) * q), + (long64)Round(m_p[m_i][m_j].Y + (normals[m_k].Y + normals[m_j].Y) * q))); + } + else + { + IntPoint pt1 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_k].X * + m_delta), (long64)Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); + IntPoint pt2 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_j].X * + m_delta), (long64)Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); + AddPoint(pt1); + AddPoint(m_p[m_i][m_j]); + AddPoint(pt2); + } +} +//------------------------------------------------------------------------------ + +void DoRound() +{ + IntPoint pt1 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), + (long64)Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); + IntPoint pt2 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), + (long64)Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); + AddPoint(pt1); + //round off reflex angles (ie > 180 deg) unless almost flat (ie < ~10deg). + if ((normals[m_k].X*normals[m_j].Y - normals[m_j].X*normals[m_k].Y) * m_delta >= 0) + { + if (normals[m_j].X * normals[m_k].X + normals[m_j].Y * normals[m_k].Y < 0.985) + { + double a1 = std::atan2(normals[m_k].Y, normals[m_k].X); + double a2 = std::atan2(normals[m_j].Y, normals[m_j].X); + if (m_delta > 0 && a2 < a1) a2 += pi *2; + else if (m_delta < 0 && a2 > a1) a2 -= pi *2; + Polygon arc = BuildArc(m_p[m_i][m_j], a1, a2, m_delta); + for (Polygon::size_type m = 0; m < arc.size(); m++) + AddPoint(arc[m]); + } + } + else + AddPoint(m_p[m_i][m_j]); + AddPoint(pt2); +} +//-------------------------------------------------------------------------- + +bool UpdateBotPt(const IntPoint &pt, IntPoint &botPt) +{ + if (pt.Y > botPt.Y || (pt.Y == botPt.Y && pt.X < botPt.X)) + { + botPt = pt; + return true; + } + else return false; +} +//-------------------------------------------------------------------------- + +}; //end PolyOffsetBuilder + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, + double delta, JoinType jointype, double MiterLimit, bool AutoFix) +{ + if (&out_polys == &in_polys) + { + Polygons poly2(in_polys); + PolyOffsetBuilder(poly2, out_polys, delta, jointype, MiterLimit, AutoFix); + } + else PolyOffsetBuilder(in_polys, out_polys, delta, jointype, MiterLimit, AutoFix); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys, PolyFillType fillType) +{ + Clipper c; + c.AddPolygon(in_poly, ptSubject); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys, PolyFillType fillType) +{ + Clipper c; + c.AddPolygons(in_polys, ptSubject); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(Polygons &polys, PolyFillType fillType) +{ + SimplifyPolygons(polys, polys, fillType); +} +//------------------------------------------------------------------------------ + +void CleanPolygon(Polygon& in_poly, Polygon& out_poly, double distance) +{ + //delta = proximity in units/pixels below which vertices + //will be stripped. Default ~= sqrt(2) so when adjacent + //vertices have both x & y coords within 1 unit, then + //the second vertex will be stripped. + int len = in_poly.size(); + if (len < 3) + out_poly.resize(0); + else + out_poly.resize(in_poly.size()); + + int d = (int)(distance * distance); + IntPoint p = in_poly[0]; + int j = 1; + for (int i = 1; i < len; i++) + { + if ((in_poly[i].X - p.X) * (in_poly[i].X - p.X) + + (in_poly[i].Y - p.Y) * (in_poly[i].Y - p.Y) <= d) + continue; + out_poly[j] = in_poly[i]; + p = in_poly[i]; + j++; + } + p = in_poly[j - 1]; + if ((in_poly[0].X - p.X) * (in_poly[0].X - p.X) + + (in_poly[0].Y - p.Y) * (in_poly[0].Y - p.Y) <= d) + j--; + if (j < len) + out_poly.resize(j); +} +//------------------------------------------------------------------------------ +void CleanPolygons(Polygons& in_polys, Polygons& out_polys, double distance) +{ + for (Polygons::size_type i = 0; i < in_polys.size(); ++i) + CleanPolygon(in_polys[i], out_polys[i], distance); +} +//------------------------------------------------------------------------------ + +void AddPolyNodeToPolygons(PolyNode& polynode, Polygons& polygons) +{ + if (polynode.Contour.size() > 0) + polygons.push_back(polynode.Contour); + for (int i = 0; i < polynode.ChildCount(); ++i) + AddPolyNodeToPolygons(*polynode.Childs[i], polygons); +} +//------------------------------------------------------------------------------ + +void PolyTreeToPolygons(PolyTree& polytree, Polygons& polygons) +{ + polygons.resize(0); + polygons.reserve(polytree.Total()); + AddPolyNodeToPolygons(polytree, polygons); +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, IntPoint& p) +{ + s << p.X << ' ' << p.Y << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, Polygon &p) +{ + for (Polygon::size_type i = 0; i < p.size(); i++) + s << p[i]; + s << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, Polygons &p) +{ + for (Polygons::size_type i = 0; i < p.size(); i++) + s << p[i]; + s << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +} //ClipperLib namespace diff --git a/clipper/cpp/clipper.hpp b/clipper/cpp/clipper.hpp new file mode 100755 index 0000000..c807efa --- /dev/null +++ b/clipper/cpp/clipper.hpp @@ -0,0 +1,340 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 5.1.0 * +* Date : 1 February 2013 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2013 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +#ifndef clipper_hpp +#define clipper_hpp + +#include +#include +#include +#include +#include + +namespace ClipperLib { + +enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; +enum PolyType { ptSubject, ptClip }; +//By far the most widely used winding rules for polygon filling are +//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) +//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) +//see http://glprogramming.com/red/chapter11.html +enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; + +typedef signed long long long64; +typedef unsigned long long ulong64; + +struct IntPoint { +public: + long64 X; + long64 Y; + IntPoint(long64 x = 0, long64 y = 0): X(x), Y(y) {}; + friend std::ostream& operator <<(std::ostream &s, IntPoint &p); +}; + +typedef std::vector< IntPoint > Polygon; +typedef std::vector< Polygon > Polygons; + + +std::ostream& operator <<(std::ostream &s, Polygon &p); +std::ostream& operator <<(std::ostream &s, Polygons &p); + +class PolyNode; +typedef std::vector< PolyNode* > PolyNodes; + +class PolyNode +{ +public: + Polygon Contour; + PolyNodes Childs; + PolyNode* Parent; + PolyNode* GetNext(); + bool IsHole(); + int ChildCount(); +private: + PolyNode* GetNextSiblingUp(); + unsigned Index; //node index in Parent.Childs + void AddChild(PolyNode& child); + friend class Clipper; //to access Index +}; + +class PolyTree: public PolyNode +{ +public: + ~PolyTree(){Clear();}; + PolyNode* GetFirst(); + void Clear(); + int Total(); +private: + PolyNodes AllNodes; + friend class Clipper; //to access AllNodes +}; + +enum JoinType { jtSquare, jtRound, jtMiter }; + +bool Orientation(const Polygon &poly); +double Area(const Polygon &poly); +void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, + double delta, JoinType jointype = jtSquare, double MiterLimit = 2, bool AutoFix = true); + +void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(Polygons &polys, PolyFillType fillType = pftEvenOdd); + +void CleanPolygon(Polygon& in_poly, Polygon& out_poly, double distance = 1.415); +void CleanPolygons(Polygons& in_polys, Polygons& out_polys, double distance = 1.415); + +void PolyTreeToPolygons(PolyTree& polytree, Polygons& polygons); + +void ReversePolygon(Polygon& p); +void ReversePolygons(Polygons& p); + +//used internally ... +enum EdgeSide { esLeft = 1, esRight = 2}; +enum IntersectProtects { ipNone = 0, ipLeft = 1, ipRight = 2, ipBoth = 3 }; + +struct TEdge { + long64 xbot; + long64 ybot; + long64 xcurr; + long64 ycurr; + long64 xtop; + long64 ytop; + double dx; + long64 deltaX; + long64 deltaY; + long64 tmpX; + PolyType polyType; + EdgeSide side; + int windDelta; //1 or -1 depending on winding direction + int windCnt; + int windCnt2; //winding count of the opposite polytype + int outIdx; + TEdge *next; + TEdge *prev; + TEdge *nextInLML; + TEdge *nextInAEL; + TEdge *prevInAEL; + TEdge *nextInSEL; + TEdge *prevInSEL; +}; + +struct IntersectNode { + TEdge *edge1; + TEdge *edge2; + IntPoint pt; + IntersectNode *next; +}; + +struct LocalMinima { + long64 Y; + TEdge *leftBound; + TEdge *rightBound; + LocalMinima *next; +}; + +struct Scanbeam { + long64 Y; + Scanbeam *next; +}; + +struct OutPt; //forward declaration + +struct OutRec { + int idx; + bool isHole; + OutRec *FirstLeft; //see comments in clipper.pas + PolyNode *polyNode; + OutPt *pts; + OutPt *bottomPt; +}; + +struct OutPt { + int idx; + IntPoint pt; + OutPt *next; + OutPt *prev; +}; + +struct JoinRec { + IntPoint pt1a; + IntPoint pt1b; + int poly1Idx; + IntPoint pt2a; + IntPoint pt2b; + int poly2Idx; +}; + +struct HorzJoinRec { + TEdge *edge; + int savedIdx; +}; + +struct IntRect { long64 left; long64 top; long64 right; long64 bottom; }; + +typedef std::vector < OutRec* > PolyOutList; +typedef std::vector < TEdge* > EdgeList; +typedef std::vector < JoinRec* > JoinList; +typedef std::vector < HorzJoinRec* > HorzJoinList; + +//ClipperBase is the ancestor to the Clipper class. It should not be +//instantiated directly. This class simply abstracts the conversion of sets of +//polygon coordinates into edge objects that are stored in a LocalMinima list. +class ClipperBase +{ +public: + ClipperBase(); + virtual ~ClipperBase(); + bool AddPolygon(const Polygon &pg, PolyType polyType); + bool AddPolygons( const Polygons &ppg, PolyType polyType); + virtual void Clear(); + IntRect GetBounds(); +protected: + void DisposeLocalMinimaList(); + TEdge* AddBoundsToLML(TEdge *e); + void PopLocalMinima(); + virtual void Reset(); + void InsertLocalMinima(LocalMinima *newLm); + LocalMinima *m_CurrentLM; + LocalMinima *m_MinimaList; + bool m_UseFullRange; + EdgeList m_edges; +}; + +class Clipper : public virtual ClipperBase +{ +public: + Clipper(); + ~Clipper(); + bool Execute(ClipType clipType, + Polygons &solution, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); + bool Execute(ClipType clipType, + PolyTree &polytree, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); + void Clear(); + bool ReverseSolution() {return m_ReverseOutput;}; + void ReverseSolution(bool value) {m_ReverseOutput = value;}; +protected: + void Reset(); + virtual bool ExecuteInternal(); +private: + PolyOutList m_PolyOuts; + JoinList m_Joins; + HorzJoinList m_HorizJoins; + ClipType m_ClipType; + Scanbeam *m_Scanbeam; + TEdge *m_ActiveEdges; + TEdge *m_SortedEdges; + IntersectNode *m_IntersectNodes; + bool m_ExecuteLocked; + PolyFillType m_ClipFillType; + PolyFillType m_SubjFillType; + bool m_ReverseOutput; + bool m_UsingPolyTree; + void DisposeScanbeamList(); + void SetWindingCount(TEdge& edge); + bool IsEvenOddFillType(const TEdge& edge) const; + bool IsEvenOddAltFillType(const TEdge& edge) const; + void InsertScanbeam(const long64 Y); + long64 PopScanbeam(); + void InsertLocalMinimaIntoAEL(const long64 botY); + void InsertEdgeIntoAEL(TEdge *edge); + void AddEdgeToSEL(TEdge *edge); + void CopyAELToSEL(); + void DeleteFromSEL(TEdge *e); + void DeleteFromAEL(TEdge *e); + void UpdateEdgeIntoAEL(TEdge *&e); + void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); + bool IsContributing(const TEdge& edge) const; + bool IsTopHorz(const long64 XPos); + void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); + void DoMaxima(TEdge *e, long64 topY); + void ProcessHorizontals(); + void ProcessHorizontal(TEdge *horzEdge); + void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + void AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + void AppendPolygon(TEdge *e1, TEdge *e2); + void DoEdge1(TEdge *edge1, TEdge *edge2, const IntPoint &pt); + void DoEdge2(TEdge *edge1, TEdge *edge2, const IntPoint &pt); + void DoBothEdges(TEdge *edge1, TEdge *edge2, const IntPoint &pt); + void IntersectEdges(TEdge *e1, TEdge *e2, + const IntPoint &pt, const IntersectProtects protects); + OutRec* CreateOutRec(); + void AddOutPt(TEdge *e, const IntPoint &pt); + void DisposeAllPolyPts(); + void DisposeOutRec(PolyOutList::size_type index); + bool ProcessIntersections(const long64 botY, const long64 topY); + void AddIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt); + void BuildIntersectList(const long64 botY, const long64 topY); + void ProcessIntersectList(); + void ProcessEdgesAtTopOfScanbeam(const long64 topY); + void BuildResult(Polygons& polys); + void BuildResult2(PolyTree& polytree); + void SetHoleState(TEdge *e, OutRec *OutRec); + void DisposeIntersectNodes(); + bool FixupIntersections(); + void FixupOutPolygon(OutRec &outRec); + bool IsHole(TEdge *e); + void FixHoleLinkage(OutRec &outRec); + void AddJoin(TEdge *e1, TEdge *e2, int e1OutIdx = -1, int e2OutIdx = -1); + void ClearJoins(); + void AddHorzJoin(TEdge *e, int idx); + void ClearHorzJoins(); + bool JoinPoints(const JoinRec *j, OutPt *&p1, OutPt *&p2); + void FixupJoinRecs(JoinRec *j, OutPt *pt, unsigned startIdx); + void JoinCommonEdges(); + void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); + void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec); +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +class clipperException : public std::exception +{ + public: + clipperException(const char* description): m_descr(description) {} + virtual ~clipperException() throw() {} + virtual const char* what() const throw() {return m_descr.c_str();} + private: + std::string m_descr; +}; +//------------------------------------------------------------------------------ + +} //ClipperLib namespace + +#endif //clipper_hpp + + diff --git a/clipper/cpp/cpp_agg/agg_conv_clipper.h b/clipper/cpp/cpp_agg/agg_conv_clipper.h new file mode 100755 index 0000000..55532ce --- /dev/null +++ b/clipper/cpp/cpp_agg/agg_conv_clipper.h @@ -0,0 +1,295 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 1.1 * +* Date : 4 April 2011 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2011 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +*******************************************************************************/ + +#ifndef AGG_CONV_CLIPPER_INCLUDED +#define AGG_CONV_CLIPPER_INCLUDED + +#include +#include "agg_basics.h" +#include "agg_array.h" +#include "../clipper.hpp" + +namespace agg +{ + enum clipper_op_e { clipper_or, + clipper_and, clipper_xor, clipper_a_minus_b, clipper_b_minus_a }; + enum clipper_PolyFillType {clipper_even_odd, clipper_non_zero, clipper_positive, clipper_negative}; + + template class conv_clipper + { + enum status { status_move_to, status_line_to, status_stop }; + typedef VSA source_a_type; + typedef VSB source_b_type; + typedef conv_clipper self_type; + + private: + source_a_type* m_src_a; + source_b_type* m_src_b; + status m_status; + int m_vertex; + int m_contour; + int m_scaling_factor; + clipper_op_e m_operation; + pod_bvector m_vertex_accumulator; + ClipperLib::Polygons m_poly_a; + ClipperLib::Polygons m_poly_b; + ClipperLib::Polygons m_result; + ClipperLib::Clipper m_clipper; + clipper_PolyFillType m_subjFillType; + clipper_PolyFillType m_clipFillType; + + int Round(double val) + { + if ((val < 0)) return (int)(val - 0.5); else return (int)(val + 0.5); + } + + public: + conv_clipper(source_a_type &a, source_b_type &b, + clipper_op_e op = clipper_or, + clipper_PolyFillType subjFillType = clipper_even_odd, + clipper_PolyFillType clipFillType = clipper_even_odd, + int scaling_factor = 2) : + m_src_a(&a), + m_src_b(&b), + m_status(status_move_to), + m_vertex(-1), + m_contour(-1), + m_operation(op), + m_subjFillType(subjFillType), + m_clipFillType(clipFillType) + { + m_scaling_factor = std::max(std::min(scaling_factor, 6),0); + m_scaling_factor = Round(std::pow((double)10, m_scaling_factor)); + } + + ~conv_clipper() + { + } + + void attach1(VSA &source, clipper_PolyFillType subjFillType = clipper_even_odd) + { m_src_a = &source; m_subjFillType = subjFillType; } + void attach2(VSB &source, clipper_PolyFillType clipFillType = clipper_even_odd) + { m_src_b = &source; m_clipFillType = clipFillType; } + + void operation(clipper_op_e v) { m_operation = v; } + + void rewind(unsigned path_id); + unsigned vertex(double* x, double* y); + + bool next_contour(); + bool next_vertex(double* x, double* y); + void start_extracting(); + void add_vertex_(double &x, double &y); + void end_contour(ClipperLib::Polygons &p); + + template void add(VS &src, ClipperLib::Polygons &p){ + unsigned cmd; + double x; double y; double start_x; double start_y; + bool starting_first_line; + + start_x = 0.0; + start_y = 0.0; + starting_first_line = true; + p.resize(0); + + cmd = src->vertex( &x , &y ); + while(!is_stop(cmd)) + { + if(is_vertex(cmd)) + { + if(is_move_to(cmd)) + { + if(!starting_first_line ) end_contour(p); + start_x = x; + start_y = y; + } + add_vertex_( x, y ); + starting_first_line = false; + } + else if(is_end_poly(cmd)) + { + if(!starting_first_line && is_closed(cmd)) + add_vertex_( start_x, start_y ); + } + cmd = src->vertex( &x, &y ); + } + end_contour(p); + } + }; + + //------------------------------------------------------------------------ + + template + void conv_clipper::start_extracting() + { + m_status = status_move_to; + m_contour = -1; + m_vertex = -1; + } + //------------------------------------------------------------------------------ + + template + void conv_clipper::rewind(unsigned path_id) + { + m_src_a->rewind( path_id ); + m_src_b->rewind( path_id ); + + add( m_src_a , m_poly_a ); + add( m_src_b , m_poly_b ); + m_result.resize(0); + + ClipperLib::PolyFillType pftSubj, pftClip; + switch (m_subjFillType) + { + case clipper_even_odd: pftSubj = ClipperLib::pftEvenOdd; break; + case clipper_non_zero: pftSubj = ClipperLib::pftNonZero; break; + case clipper_positive: pftSubj = ClipperLib::pftPositive; break; + default: pftSubj = ClipperLib::pftNegative; + } + switch (m_clipFillType) + { + case clipper_even_odd: pftClip = ClipperLib::pftEvenOdd; break; + case clipper_non_zero: pftClip = ClipperLib::pftNonZero; break; + case clipper_positive: pftClip = ClipperLib::pftPositive; break; + default: pftClip = ClipperLib::pftNegative; + } + + m_clipper.Clear(); + switch( m_operation ) { + case clipper_or: + { + m_clipper.AddPolygons( m_poly_a , ClipperLib::ptSubject ); + m_clipper.AddPolygons( m_poly_b , ClipperLib::ptClip ); + m_clipper.Execute( ClipperLib::ctUnion , m_result , pftSubj, pftClip); + break; + } + case clipper_and: + { + m_clipper.AddPolygons( m_poly_a , ClipperLib::ptSubject ); + m_clipper.AddPolygons( m_poly_b , ClipperLib::ptClip ); + m_clipper.Execute( ClipperLib::ctIntersection , m_result, pftSubj, pftClip ); + break; + } + case clipper_xor: + { + m_clipper.AddPolygons( m_poly_a , ClipperLib::ptSubject ); + m_clipper.AddPolygons( m_poly_b , ClipperLib::ptClip ); + m_clipper.Execute( ClipperLib::ctXor , m_result, pftSubj, pftClip ); + break; + } + case clipper_a_minus_b: + { + m_clipper.AddPolygons( m_poly_a , ClipperLib::ptSubject ); + m_clipper.AddPolygons( m_poly_b , ClipperLib::ptClip ); + m_clipper.Execute( ClipperLib::ctDifference , m_result, pftSubj, pftClip ); + break; + } + case clipper_b_minus_a: + { + m_clipper.AddPolygons( m_poly_b , ClipperLib::ptSubject ); + m_clipper.AddPolygons( m_poly_a , ClipperLib::ptClip ); + m_clipper.Execute( ClipperLib::ctDifference , m_result, pftSubj, pftClip ); + break; + } + } + start_extracting(); + } + //------------------------------------------------------------------------------ + + template + void conv_clipper::end_contour( ClipperLib::Polygons &p) + { + unsigned i, len; + + if( m_vertex_accumulator.size() < 3 ) return; + len = p.size(); + p.resize(len+1); + p[len].resize(m_vertex_accumulator.size()); + for( i = 0 ; i < m_vertex_accumulator.size() ; i++ ) + p[len][i] = m_vertex_accumulator[i]; + m_vertex_accumulator.remove_all(); + } + //------------------------------------------------------------------------------ + + template + void conv_clipper::add_vertex_(double &x, double &y) + { + ClipperLib::IntPoint v; + + v.X = Round(x * m_scaling_factor); + v.Y = Round(y * m_scaling_factor); + m_vertex_accumulator.add( v ); + } + //------------------------------------------------------------------------------ + + template + bool conv_clipper::next_contour() + { + m_contour++; + if(m_contour >= (int)m_result.size()) return false; + m_vertex =-1; + return true; +} +//------------------------------------------------------------------------------ + + template + bool conv_clipper::next_vertex(double *x, double *y) + { + m_vertex++; + if(m_vertex >= (int)m_result[m_contour].size()) return false; + *x = (double)m_result[ m_contour ][ m_vertex ].X / m_scaling_factor; + *y = (double)m_result[ m_contour ][ m_vertex ].Y / m_scaling_factor; + return true; + } + //------------------------------------------------------------------------------ + + template + unsigned conv_clipper::vertex(double *x, double *y) +{ + if( m_status == status_move_to ) + { + if( next_contour() ) + { + if( next_vertex( x, y ) ) + { + m_status =status_line_to; + return path_cmd_move_to; + } + else + { + m_status = status_stop; + return path_cmd_end_poly | path_flags_close; + } + } + else + return path_cmd_stop; + } + else + { + if( next_vertex( x, y ) ) + { + return path_cmd_line_to; + } + else + { + m_status = status_move_to; + return path_cmd_end_poly | path_flags_close; + } + } +} +//------------------------------------------------------------------------------ + + +} //namespace agg +#endif //AGG_CONV_CLIPPER_INCLUDED diff --git a/clipper/cpp/cpp_agg/clipper_test.cpp b/clipper/cpp/cpp_agg/clipper_test.cpp new file mode 100755 index 0000000..ffbaa64 --- /dev/null +++ b/clipper/cpp/cpp_agg/clipper_test.cpp @@ -0,0 +1,574 @@ +#include +#include "agg_basics.h" +#include "agg_rendering_buffer.h" +#include "agg_rasterizer_scanline_aa.h" +#include "agg_scanline_u.h" +#include "agg_scanline_p.h" +#include "agg_renderer_scanline.h" +#include "agg_renderer_primitives.h" +#include "agg_conv_curve.h" +#include "agg_conv_stroke.h" +#include "agg_conv_clip_polygon.h" +#include "agg_gsv_text.h" +#include "agg_pixfmt_rgb.h" +#include "agg_platform_support.h" + +#include "agg_slider_ctrl.h" +#include "agg_cbox_ctrl.h" +#include "agg_rbox_ctrl.h" + +#include "agg_conv_clipper.h" +#include "windows.h" + +enum flip_y_e { flip_y = true }; + + +class spiral +{ +public: + spiral(double x, double y, double r1, double r2, double step, double start_angle=0) : + m_x(x), + m_y(y), + m_r1(r1), + m_r2(r2), + m_step(step), + m_start_angle(start_angle), + m_angle(start_angle), + m_da(agg::deg2rad(4.0)), + m_dr(m_step / 90.0) + { + } + + void rewind(unsigned) + { + m_angle = m_start_angle; + m_curr_r = m_r1; + m_start = true; + } + + unsigned vertex(double* x, double* y) + { + if(m_curr_r > m_r2) return agg::path_cmd_stop; + + *x = m_x + cos(m_angle) * m_curr_r; + *y = m_y + sin(m_angle) * m_curr_r; + m_curr_r += m_dr; + m_angle += m_da; + if(m_start) + { + m_start = false; + return agg::path_cmd_move_to; + } + return agg::path_cmd_line_to; + } + +private: + double m_x; + double m_y; + double m_r1; + double m_r2; + double m_step; + double m_start_angle; + + double m_angle; + double m_curr_r; + double m_da; + double m_dr; + bool m_start; +}; + + + +namespace agg +{ + // A simple counter of points and contours + template struct conv_poly_counter + { + unsigned m_contours; + unsigned m_points; + + conv_poly_counter(Src& src) : m_src(&src), m_contours(0), m_points(0) {} + + void rewind(unsigned path_id) + { + m_contours = 0; + m_points = 0; + m_src->rewind(path_id); + } + + unsigned vertex(double* x, double* y) + { + unsigned cmd = m_src->vertex(x, y); + if(is_vertex(cmd)) ++m_points; + if(is_move_to(cmd)) ++m_contours; + return cmd; + } + + private: + Src* m_src; + }; +} + + +void make_gb_poly(agg::path_storage& ps); +void make_arrows(agg::path_storage& ps); + + +class the_application : public agg::platform_support +{ + agg::rbox_ctrl m_polygons; + agg::rbox_ctrl m_operation; + double m_x; + double m_y; + + virtual void on_key(int x, int y, unsigned key, unsigned flags) + { + if(key == agg::key_escape) exit(0); + + } + +public: + the_application(agg::pix_format_e format, bool flip_y) : + agg::platform_support(format, flip_y), + m_polygons (5.0, 5.0, 5.0+205.0, 110.0, !flip_y), + m_operation(555.0, 5.0, 555.0+80.0, 130.0, !flip_y) + { + m_operation.add_item("None"); + m_operation.add_item("OR"); + m_operation.add_item("AND"); + m_operation.add_item("XOR"); + m_operation.add_item("A-B"); + m_operation.add_item("B-A"); + m_operation.cur_item(2); + add_ctrl(m_operation); + + m_polygons.add_item("Two Simple Paths"); + m_polygons.add_item("Closed Stroke"); + m_polygons.add_item("Great Britain and Arrows"); + m_polygons.add_item("Great Britain and Spiral"); + m_polygons.add_item("Spiral and Glyph"); + m_polygons.cur_item(3); + add_ctrl(m_polygons); + } + + + template + void perform_rendering(Scanline &sl, Ras &ras, Ren &ren, Clp &clp) + { + if(m_operation.cur_item() > 0) + { + ras.reset(); + switch(m_operation.cur_item()) + { + case 1: clp.operation(agg::clipper_or); break; + case 2: clp.operation(agg::clipper_and); break; + case 3: clp.operation(agg::clipper_xor); break; + case 4: clp.operation(agg::clipper_a_minus_b); break; + case 5: clp.operation(agg::clipper_b_minus_a); break; + } + agg::conv_poly_counter counter(clp); + + + start_timer(); + counter.rewind(0); + double t1 = elapsed_time(); + + agg::path_storage ps; + double x; + double y; + unsigned cmd; + start_timer(); + while(!agg::is_stop(cmd = counter.vertex(&x, &y))) + { + if(agg::is_move_to(cmd)) + ps.move_to(x, y); + else if(agg::is_line_to(cmd)) + ps.line_to(x, y); + else if(agg::is_close(cmd)) + ps.close_polygon(); + } + ras.add_path(ps); + ren.color(agg::rgba(0.25, 0.9, 0.25, 0.65)); + agg::render_scanlines(ras, sl, ren); + double t2 = elapsed_time(); + + agg::conv_stroke stroke(ps); + stroke.width(0.4); + ras.add_path(stroke); + ren.color(agg::rgba(0, 0, 0)); + agg::render_scanlines(ras, sl, ren); + + char buf[100]; + sprintf_s(buf, "Contours: %d Points: %d", counter.m_contours, counter.m_points); + agg::gsv_text txt; + agg::conv_stroke txt_stroke(txt); + txt_stroke.width(1.5); + txt_stroke.line_cap(agg::round_cap); + txt.size(10.0); + txt.start_point(250, 5); + txt.text(buf); + ras.add_path(txt_stroke); + ren.color(agg::rgba(0.0, 0.0, 0.0)); + agg::render_scanlines(ras, sl, ren); + + sprintf_s(buf, "Clipper=%.3fms Render=%.3fms", t1, t2); + txt.start_point(250, 20); + txt.text(buf); + ras.add_path(txt_stroke); + ren.color(agg::rgba(0.0, 0.0, 0.0)); + agg::render_scanlines(ras, sl, ren); + } + } + + + template + unsigned render_clipper(Scanline& sl, Ras& ras) + { + agg::pixfmt_bgr24 pf(rbuf_window()); + agg::renderer_base rb(pf); + agg::renderer_scanline_aa_solid > ren(rb); + + + switch(m_polygons.cur_item()) + { + case 0: + { + //------------------------------------ + // Two simple paths + // + agg::path_storage ps1; + agg::path_storage ps2; + + agg::conv_clipper clp(ps1, ps2, agg::clipper_or, agg::clipper_non_zero, agg::clipper_non_zero); + + double x = m_x - initial_width()/2 + 100; + double y = m_y - initial_height()/2 + 100; + ps1.move_to(x+140, y+145); + ps1.line_to(x+225, y+44); + ps1.line_to(x+296, y+219); + ps1.close_polygon(); + + ps1.line_to(x+226, y+289); + ps1.line_to(x+82, y+292); + + ps1.move_to(x+220, y+222); + ps1.line_to(x+363, y+249); + ps1.line_to(x+265, y+331); + + ps1.move_to(x+242, y+243); + ps1.line_to(x+268, y+309); + ps1.line_to(x+325, y+261); + + ps1.move_to(x+259, y+259); + ps1.line_to(x+273, y+288); + ps1.line_to(x+298, y+266); + + ps2.move_to(100+32, 100+77); + ps2.line_to(100+473, 100+263); + ps2.line_to(100+351, 100+290); + ps2.line_to(100+354, 100+374); + + ras.reset(); + ras.add_path(ps1); + ren.color(agg::rgba(0, 0, 0, 0.1)); + agg::render_scanlines(ras, sl, ren); + + ras.reset(); + ras.add_path(ps2); + ren.color(agg::rgba(0, 0.6, 0, 0.1)); + agg::render_scanlines(ras, sl, ren); + + perform_rendering(sl, ras, ren, clp); + } + break; + + case 1: + { + //------------------------------------ + // Closed stroke + // + agg::path_storage ps1; + agg::path_storage ps2; + agg::conv_stroke stroke(ps2); + stroke.width(10.0); + + agg::conv_clipper > clp(ps1, stroke, agg::clipper_or, agg::clipper_non_zero, agg::clipper_non_zero); + + + double x = m_x - initial_width()/2 + 100; + double y = m_y - initial_height()/2 + 100; + ps1.move_to(x+140, y+145); + ps1.line_to(x+225, y+44); + ps1.line_to(x+296, y+219); + ps1.close_polygon(); + + ps1.line_to(x+226, y+289); + ps1.line_to(x+82, y+292); + + ps1.move_to(x+220-50, y+222); + ps1.line_to(x+265-50, y+331); + ps1.line_to(x+363-50, y+249); + ps1.close_polygon(agg::path_flags_ccw); + + ps2.move_to(100+32, 100+77); + ps2.line_to(100+473, 100+263); + ps2.line_to(100+351, 100+290); + ps2.line_to(100+354, 100+374); + ps2.close_polygon(); + + ras.reset(); + ras.add_path(ps1); + ren.color(agg::rgba(0, 0, 0, 0.1)); + agg::render_scanlines(ras, sl, ren); + + ras.reset(); + ras.add_path(stroke); + ren.color(agg::rgba(0, 0.6, 0, 0.1)); + agg::render_scanlines(ras, sl, ren); + + perform_rendering(sl, ras, ren, clp); + } + break; + + + case 2: + { + //------------------------------------ + // Great Britain and Arrows + // + agg::path_storage gb_poly; + agg::path_storage arrows; + make_gb_poly(gb_poly); + make_arrows(arrows); + + agg::trans_affine mtx1; + agg::trans_affine mtx2; + mtx1 *= agg::trans_affine_translation(-1150, -1150); + mtx1 *= agg::trans_affine_scaling(2.0); + + mtx2 = mtx1; + mtx2 *= agg::trans_affine_translation(m_x - initial_width()/2, + m_y - initial_height()/2); + + agg::conv_transform trans_gb_poly(gb_poly, mtx1); + agg::conv_transform trans_arrows(arrows, mtx2); + + agg::conv_clipper, + agg::conv_transform > clp(trans_gb_poly, trans_arrows, agg::clipper_or, agg::clipper_non_zero, agg::clipper_non_zero); + + ras.add_path(trans_gb_poly); + ren.color(agg::rgba(0.5, 0.5, 0, 0.1)); + agg::render_scanlines(ras, sl, ren); + + agg::conv_stroke > stroke_gb_poly(trans_gb_poly); + stroke_gb_poly.width(0.1); + ras.add_path(stroke_gb_poly); + ren.color(agg::rgba(0, 0, 0)); + agg::render_scanlines(ras, sl, ren); + + ras.add_path(trans_arrows); + ren.color(agg::rgba(0.0, 0.5, 0.5, 0.1)); + agg::render_scanlines(ras, sl, ren); + + perform_rendering(sl, ras, ren, clp); + } + break; + + + case 3: + { + //------------------------------------ + // Great Britain and a Spiral + // + spiral sp(m_x, m_y, 10, 150, 30, 0.0); + agg::conv_stroke stroke(sp); + stroke.width(15.0); + + agg::path_storage gb_poly; + make_gb_poly(gb_poly); + + agg::trans_affine mtx; + mtx *= agg::trans_affine_translation(-1150, -1150); + mtx *= agg::trans_affine_scaling(2.0); + + agg::conv_transform trans_gb_poly(gb_poly, mtx); + + agg::conv_clipper, + agg::conv_stroke > clp(trans_gb_poly, stroke, agg::clipper_or, agg::clipper_non_zero, agg::clipper_non_zero); + + ras.add_path(trans_gb_poly); + ren.color(agg::rgba(0.5, 0.5, 0, 0.1)); + agg::render_scanlines(ras, sl, ren); + + agg::conv_stroke > stroke_gb_poly(trans_gb_poly); + stroke_gb_poly.width(0.1); + ras.add_path(stroke_gb_poly); + ren.color(agg::rgba(0, 0, 0)); + agg::render_scanlines(ras, sl, ren); + + ras.add_path(stroke); + ren.color(agg::rgba(0.0, 0.5, 0.5, 0.1)); + agg::render_scanlines(ras, sl, ren); + + perform_rendering(sl, ras, ren, clp); + } + break; + + + case 4: + { + //------------------------------------ + // Spiral and glyph + // + spiral sp(m_x, m_y, 10, 150, 30, 0.0); + agg::conv_stroke stroke(sp); + stroke.width(15.0); + + agg::path_storage glyph; + glyph.move_to(28.47, 6.45); + glyph.curve3(21.58, 1.12, 19.82, 0.29); + glyph.curve3(17.19, -0.93, 14.21, -0.93); + glyph.curve3(9.57, -0.93, 6.57, 2.25); + glyph.curve3(3.56, 5.42, 3.56, 10.60); + glyph.curve3(3.56, 13.87, 5.03, 16.26); + glyph.curve3(7.03, 19.58, 11.99, 22.51); + glyph.curve3(16.94, 25.44, 28.47, 29.64); + glyph.line_to(28.47, 31.40); + glyph.curve3(28.47, 38.09, 26.34, 40.58); + glyph.curve3(24.22, 43.07, 20.17, 43.07); + glyph.curve3(17.09, 43.07, 15.28, 41.41); + glyph.curve3(13.43, 39.75, 13.43, 37.60); + glyph.line_to(13.53, 34.77); + glyph.curve3(13.53, 32.52, 12.38, 31.30); + glyph.curve3(11.23, 30.08, 9.38, 30.08); + glyph.curve3(7.57, 30.08, 6.42, 31.35); + glyph.curve3(5.27, 32.62, 5.27, 34.81); + glyph.curve3(5.27, 39.01, 9.57, 42.53); + glyph.curve3(13.87, 46.04, 21.63, 46.04); + glyph.curve3(27.59, 46.04, 31.40, 44.04); + glyph.curve3(34.28, 42.53, 35.64, 39.31); + glyph.curve3(36.52, 37.21, 36.52, 30.71); + glyph.line_to(36.52, 15.53); + glyph.curve3(36.52, 9.13, 36.77, 7.69); + glyph.curve3(37.01, 6.25, 37.57, 5.76); + glyph.curve3(38.13, 5.27, 38.87, 5.27); + glyph.curve3(39.65, 5.27, 40.23, 5.62); + glyph.curve3(41.26, 6.25, 44.19, 9.18); + glyph.line_to(44.19, 6.45); + glyph.curve3(38.72, -0.88, 33.74, -0.88); + glyph.curve3(31.35, -0.88, 29.93, 0.78); + glyph.curve3(28.52, 2.44, 28.47, 6.45); + glyph.close_polygon(); + + glyph.move_to(28.47, 9.62); + glyph.line_to(28.47, 26.66); + glyph.curve3(21.09, 23.73, 18.95, 22.51); + glyph.curve3(15.09, 20.36, 13.43, 18.02); + glyph.curve3(11.77, 15.67, 11.77, 12.89); + glyph.curve3(11.77, 9.38, 13.87, 7.06); + glyph.curve3(15.97, 4.74, 18.70, 4.74); + glyph.curve3(22.41, 4.74, 28.47, 9.62); + glyph.close_polygon(); + + agg::trans_affine mtx; + mtx *= agg::trans_affine_scaling(4.0); + mtx *= agg::trans_affine_translation(220, 200); + agg::conv_transform trans(glyph, mtx); + agg::conv_curve > curve(trans); + + agg::conv_clipper, + agg::conv_curve< + agg::conv_transform< + agg::path_storage> > > clp(stroke, curve, agg::clipper_or, agg::clipper_non_zero, agg::clipper_non_zero); + + ras.reset(); + ras.add_path(stroke); + ren.color(agg::rgba(0, 0, 0, 0.1)); + agg::render_scanlines(ras, sl, ren); + + ras.reset(); + ras.add_path(curve); + ren.color(agg::rgba(0, 0.6, 0, 0.1)); + agg::render_scanlines(ras, sl, ren); + + perform_rendering(sl, ras, ren, clp); + } + break; + } + + return 0; + } + + + virtual void on_init() + { + m_x = width() / 2.0; + m_y = height() / 2.0; + } + + virtual void on_draw() + { + typedef agg::renderer_base base_ren_type; + + agg::pixfmt_bgr24 pf(rbuf_window()); + base_ren_type ren_base(pf); + ren_base.clear(agg::rgba(1,1,1)); + + agg::scanline_u8 sl; + agg::rasterizer_scanline_aa<> ras; + + render_clipper(sl, ras); + + agg::render_ctrl(ras, sl, ren_base, m_polygons); + agg::render_ctrl(ras, sl, ren_base, m_operation); + } + + virtual void on_mouse_button_down(int x, int y, unsigned flags) + { + if(flags & agg::mouse_left) + { + m_x = x; + m_y = y; + force_redraw(); + } + } + + + virtual void on_mouse_move(int x, int y, unsigned flags) + { + if(flags & agg::mouse_left) + { + m_x = x; + m_y = y; + force_redraw(); + } + } + + + +}; + + +int agg_main(int argc, char* argv[]) +{ + the_application app(agg::pix_format_bgr24, flip_y); + app.caption("AGG Example. Clipper"); + + if(app.init(640, 520, agg::window_resize)) + { + //replace the main window icon with Resource Icon #1 ... + HWND w = GetActiveWindow(); + HMODULE m = GetModuleHandle(0); //hInstance + HANDLE small_ico = LoadImage(m, MAKEINTRESOURCE(1), IMAGE_ICON, 16, 16, 0); + HANDLE big_ico = LoadImage(m, MAKEINTRESOURCE(1), IMAGE_ICON, 32, 32, 0); + SendMessage(w, WM_SETICON, ICON_SMALL, (LPARAM)small_ico); + SendMessage(w, WM_SETICON, ICON_BIG, (LPARAM)big_ico); + + //main message loop ... + return app.run(); + } + return 0; +} + + diff --git a/clipper/cpp/cpp_agg/clipper_test.exe b/clipper/cpp/cpp_agg/clipper_test.exe new file mode 100755 index 0000000..2311a43 Binary files /dev/null and b/clipper/cpp/cpp_agg/clipper_test.exe differ diff --git a/clipper/cpp/cpp_agg/clipper_test.sln b/clipper/cpp/cpp_agg/clipper_test.sln new file mode 100755 index 0000000..2c46499 --- /dev/null +++ b/clipper/cpp/cpp_agg/clipper_test.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual C++ Express 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "clipper_test", "clipper_test.vcxproj", "{4DF13A7A-6137-E76C-8C62-F591D0E4B69F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4DF13A7A-6137-E76C-8C62-F591D0E4B69F}.Debug|Win32.ActiveCfg = Debug|Win32 + {4DF13A7A-6137-E76C-8C62-F591D0E4B69F}.Debug|Win32.Build.0 = Debug|Win32 + {4DF13A7A-6137-E76C-8C62-F591D0E4B69F}.Release|Win32.ActiveCfg = Release|Win32 + {4DF13A7A-6137-E76C-8C62-F591D0E4B69F}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/clipper/cpp/cpp_agg/clipper_test.vcxproj b/clipper/cpp/cpp_agg/clipper_test.vcxproj new file mode 100755 index 0000000..9f4aaf2 --- /dev/null +++ b/clipper/cpp/cpp_agg/clipper_test.vcxproj @@ -0,0 +1,114 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Win32Proj + clipper_test + + + + Application + true + + + Application + false + + + + + + + + + + + + + true + C:\Program Files (x86)\Borland\agg-2.5\include;C:\Program Files (x86)\Borland\agg-2.5\include\platform;C:\Program Files (x86)\Borland\agg-2.5\include\platform\win32;C:\Program Files (x86)\Borland\agg-2.5\include\ctrl;C:\Program Files (x86)\Borland\agg-2.5\include\util;$(IncludePath) + + + true + C:\Program Files (x86)\Borland\agg-2.5\include;C:\Program Files (x86)\Borland\agg-2.5\include\platform;C:\Program Files (x86)\Borland\agg-2.5\include\platform\win32;C:\Program Files (x86)\Borland\agg-2.5\include\ctrl;C:\Program Files (x86)\Borland\agg-2.5\include\util;$(IncludePath) + + + + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + ProgramDatabase + Disabled + + + MachineX86 + true + Windows + + + + + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + + + MachineX86 + true + Windows + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/clipper/cpp/cpp_agg/clipper_test.vcxproj.filters b/clipper/cpp/cpp_agg/clipper_test.vcxproj.filters new file mode 100755 index 0000000..6a12557 --- /dev/null +++ b/clipper/cpp/cpp_agg/clipper_test.vcxproj.filters @@ -0,0 +1,118 @@ + + + + + + ClipperLib + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + agg_src + + + + + {31703eea-b46a-4b7c-93fe-7b3889404c6f} + + + {0d7823fd-c31f-4f5d-b1f5-3f3c91751bf3} + + + {0ea31d0f-7e25-4f22-964b-8c74b5ced2fd} + + + + + ClipperLib + + + agg_clipper + + + \ No newline at end of file diff --git a/clipper/cpp/cpp_agg/icon.res b/clipper/cpp/cpp_agg/icon.res new file mode 100755 index 0000000..a4fc1a7 Binary files /dev/null and b/clipper/cpp/cpp_agg/icon.res differ diff --git a/clipper/cpp/cpp_cairo/Cairo Resources.txt b/clipper/cpp/cpp_cairo/Cairo Resources.txt new file mode 100755 index 0000000..e868eec --- /dev/null +++ b/clipper/cpp/cpp_cairo/Cairo Resources.txt @@ -0,0 +1,6 @@ +http://cairographics.org/ + +The Windows dynamic linked libraries necessary to run Cairo can be downloaded from +http://www.gtk.org/download/win32.php +All the dlls listed under the heading "Required third party dependencies" are +required except gettext-runtime.dll. diff --git a/clipper/cpp/cpp_cairo/cairo_clipper.cbproj b/clipper/cpp/cpp_cairo/cairo_clipper.cbproj new file mode 100755 index 0000000..e781217 --- /dev/null +++ b/clipper/cpp/cpp_cairo/cairo_clipper.cbproj @@ -0,0 +1,155 @@ + + + {EA7CCDE7-B2DF-458D-BEFC-CBA442BF2E3A} + 12.0 + Release + + + true + + + true + Base + true + + + true + Base + true + + + exe + vcl.bpi;rtl.bpi;vclx.bpi;vclactnband.bpi;dbrtl.bpi;vcldb.bpi;vcldbx.bpi;bdertl.bpi;dsnap.bpi;dsnapcon.bpi;TeeUI.bpi;TeeDB.bpi;Tee.bpi;adortl.bpi;IndyCore.bpi;IndySystem.bpi;IndyProtocols.bpi;VclSmp.bpi;dbexpress.bpi;DbxCommonDriver.bpi;DataSnapIndy10ServerTransport.bpi;DataSnapProviderClient.bpi;DataSnapServer.bpi;DbxClientDriver.bpi;DBXInterBaseDriver.bpi;DBXMySQLDriver.bpi;dbxcds.bpi;DBXSybaseASEDriver.bpi;DBXSybaseASADriver.bpi;DBXOracleDriver.bpi;DBXMSSQLDriver.bpi;DBXInformixDriver.bpi;DBXDb2Driver.bpi;bcbie.bpi;xmlrtl.bpi;bcbsmp.bpi;GR32_CB6.bpi;GR32_DSGN_CB6.bpi + rtl.lib + true + rtl.lib + CppGuiApplication + ..\cairo_src;..\cairo_dlls;$(BCC_IncludePath) + JPHNE + NO_STRICT + true + ..\src;..\..\cpp;$(BDS)\include;$(BDS)\include\dinkumware;$(BDS)\include\vcl;..\cairo_dlls;$(IncludePath) + ..\..\cpp;$(BDS)\lib\obj;$(BDS)\lib\psdk;..\cairo_src;$(ILINK_LibraryPath) + false + true + + + false + true + false + true + _DEBUG;$(Defines) + false + Debug + true + None + DEBUG + true + true + true + $(BDS)\lib\debug;$(ILINK_LibraryPath) + true + Full + true + + + NDEBUG;$(Defines) + Release + $(BDS)\lib\release;$(ILINK_LibraryPath) + None + + + + 7 + + + 6 + + + 7 + + + 6 + + + 0 + + + true + 1 + + + Base + + + Cfg_2 + Base + + + Cfg_1 + Base + + + + + CPlusPlusBuilder.Personality.12 + CppGuiApplication + + + + False + False + 1 + 0 + 0 + 0 + False + False + False + False + False + 3081 + 1252 + + + + + 1.0.0.0 + + + + + + 1.0.0.0 + + + + + + + + + False + + + + + + + False + + False + + True + False + + + False + True + True + + + + 12 + + diff --git a/clipper/cpp/cpp_cairo/cairo_clipper.cpp b/clipper/cpp/cpp_cairo/cairo_clipper.cpp new file mode 100755 index 0000000..d373b1b --- /dev/null +++ b/clipper/cpp/cpp_cairo/cairo_clipper.cpp @@ -0,0 +1,134 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 1.1 * +* Date : 4 April 2011 * +* Copyright : Angus Johnson 2010-2011 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Modified by Mike Owens to support coordinate transformation * +*******************************************************************************/ + +#include +#include +#include +#include "clipper.hpp" +#include "cairo_clipper.hpp" + +namespace ClipperLib { + namespace cairo { + + namespace { + + inline long64 Round(double val) + { + if ((val < 0)) return (long64)(val - 0.5); else return (long64)(val + 0.5); + } + + void transform_point(cairo_t* pen, Transform transform, long64* x, long64* y) + { + double _x = (double)*x, _y = (double)*y; + switch (transform) + { + case tDeviceToUser: + cairo_device_to_user(pen, &_x, &_y); + break; + case tUserToDevice: + cairo_user_to_device(pen, &_x, &_y); + break; + default: + ; + } + *x = Round(_x); *y = Round(_y); + } + } + + void cairo_to_clipper(cairo_t* cr, + Polygons &pg, + int scaling_factor, + Transform transform) + { + if (scaling_factor > 8 || scaling_factor < 0) + throw clipperCairoException("cairo_to_clipper: invalid scaling factor"); + double scaling = std::pow((double)10, scaling_factor); + + pg.clear(); + cairo_path_t *path = cairo_copy_path_flat(cr); + + int poly_count = 0; + for (int i = 0; i < path->num_data; i += path->data[i].header.length) { + if( path->data[i].header.type == CAIRO_PATH_CLOSE_PATH) poly_count++; + } + + pg.resize(poly_count); + int i = 0, pc = 0; + while (pc < poly_count) + { + int vert_count = 1; + int j = i; + while(j < path->num_data && + path->data[j].header.type != CAIRO_PATH_CLOSE_PATH) + { + if (path->data[j].header.type == CAIRO_PATH_LINE_TO) + vert_count++; + j += path->data[j].header.length; + } + pg[pc].resize(vert_count); + if (path->data[i].header.type != CAIRO_PATH_MOVE_TO) { + pg.resize(pc); + break; + } + pg[pc][0].X = Round(path->data[i+1].point.x *scaling); + pg[pc][0].Y = Round(path->data[i+1].point.y *scaling); + if (transform != tNone) + transform_point(cr, transform, &pg[pc][0].X, &pg[pc][0].Y); + + i += path->data[i].header.length; + + j = 1; + while (j < vert_count && i < path->num_data && + path->data[i].header.type == CAIRO_PATH_LINE_TO) { + pg[pc][j].X = Round(path->data[i+1].point.x *scaling); + pg[pc][j].Y = Round(path->data[i+1].point.y *scaling); + if (transform != tNone) + transform_point(cr, transform, &pg[pc][j].X, &pg[pc][j].Y); + j++; + i += path->data[i].header.length; + } + pc++; + i += path->data[i].header.length; + } + cairo_path_destroy(path); + } + //-------------------------------------------------------------------------- + + void clipper_to_cairo(const Polygons &pg, + cairo_t* cr, + int scaling_factor, + Transform transform) + { + if (scaling_factor > 8 || scaling_factor < 0) + throw clipperCairoException("clipper_to_cairo: invalid scaling factor"); + double scaling = std::pow((double)10, scaling_factor); + for (size_t i = 0; i < pg.size(); ++i) + { + size_t sz = pg[i].size(); + if (sz < 3) + continue; + cairo_new_sub_path(cr); + for (size_t j = 0; j < sz; ++j) { + long64 x = pg[i][j].X, y = pg[i][j].Y; + if (transform != tNone) + transform_point(cr, transform, &x, &y); + cairo_line_to(cr, (double)x / scaling, (double)y / scaling); + } + cairo_close_path(cr); + } + } + //-------------------------------------------------------------------------- + + } +} diff --git a/clipper/cpp/cpp_cairo/cairo_clipper.hpp b/clipper/cpp/cpp_cairo/cairo_clipper.hpp new file mode 100755 index 0000000..d767da2 --- /dev/null +++ b/clipper/cpp/cpp_cairo/cairo_clipper.hpp @@ -0,0 +1,59 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 1.1 * +* Date : 4 April 2011 * +* Copyright : Angus Johnson 2010-2011 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Modified by Mike Owens to support coordinate transformation * +*******************************************************************************/ + +#ifndef CLIPPER_CAIRO_CLIPPER_HPP +#define CLIPPER_CAIRO_CLIPPER_HPP + +#include "clipper.hpp" + +typedef struct _cairo cairo_t; + +namespace ClipperLib { + namespace cairo { + + enum Transform { + tNone, + tUserToDevice, + tDeviceToUser + }; + +//nb: Since Clipper only accepts integer coordinates, fractional values have to +//be scaled up and down when being passed to and from Clipper. This is easily +//accomplished by setting the scaling factor (10^x) in the following functions. +//When scaling, remember that on most platforms, integer is only a 32bit value. + void cairo_to_clipper(cairo_t* cr, + ClipperLib::Polygons &pg, + int scaling_factor = 0, + Transform transform = tNone); + + void clipper_to_cairo(const ClipperLib::Polygons &pg, + cairo_t* cr, + int scaling_factor = 0, + Transform transform = tNone); + } + + class clipperCairoException : public std::exception + { + public: + clipperCairoException(const char* description) + throw(): std::exception(), m_description (description) {} + virtual ~clipperCairoException() throw() {} + virtual const char* what() const throw() {return m_description.c_str();} + private: + std::string m_description; + }; +} + +#endif + diff --git a/clipper/cpp/cpp_cairo/libcairo-2.lib (cppbuilder) b/clipper/cpp/cpp_cairo/libcairo-2.lib (cppbuilder) new file mode 100755 index 0000000..205f875 Binary files /dev/null and b/clipper/cpp/cpp_cairo/libcairo-2.lib (cppbuilder) differ diff --git a/clipper/cpp/cpp_cairo/libcairo-2.lib (msvc) b/clipper/cpp/cpp_cairo/libcairo-2.lib (msvc) new file mode 100755 index 0000000..9be9bc1 Binary files /dev/null and b/clipper/cpp/cpp_cairo/libcairo-2.lib (msvc) differ diff --git a/clipper/cpp/cpp_cairo/main.cpp b/clipper/cpp/cpp_cairo/main.cpp new file mode 100755 index 0000000..13a6012 --- /dev/null +++ b/clipper/cpp/cpp_cairo/main.cpp @@ -0,0 +1,180 @@ +//--------------------------------------------------------------------------- + +#include +#include +#include +#include +#pragma hdrstop + +#include "clipper.hpp" +#include "cairo.h" +#include "cairo-win32.h" +#include "cairo_clipper.hpp" +//--------------------------------------------------------------------------- + +LPCTSTR ClsName = "CairoApp"; +LPCTSTR WndName = "A Simple Cairo Clipper Demo"; +int offsetVal = 0; + +LRESULT CALLBACK WndProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +int CALLBACK WinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) +{ + MSG Msg; + HWND hWnd; + WNDCLASSEX WndClsEx; + + // Create the application window + WndClsEx.cbSize = sizeof(WNDCLASSEX); + WndClsEx.style = CS_HREDRAW | CS_VREDRAW; + WndClsEx.lpfnWndProc = WndProcedure; + WndClsEx.cbClsExtra = 0; + WndClsEx.cbWndExtra = 0; + WndClsEx.hIcon = LoadIcon(NULL, IDI_APPLICATION); + WndClsEx.hCursor = LoadCursor(NULL, IDC_ARROW); + WndClsEx.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + WndClsEx.lpszMenuName = NULL; + WndClsEx.lpszClassName = ClsName; + WndClsEx.hInstance = hInstance; + WndClsEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION); + + // Register the application + RegisterClassEx(&WndClsEx); + + // Create the window object + hWnd = CreateWindow(ClsName, WndName, WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, + NULL, NULL, hInstance, NULL); + if( !hWnd ) return 0; + + ShowWindow(hWnd, SW_SHOWNORMAL); + UpdateWindow(hWnd); + + while( GetMessage(&Msg, NULL, 0, 0) ) + { + TranslateMessage(&Msg); + DispatchMessage(&Msg); + } + return Msg.wParam; +} +//------------------------------------------------------------------------------ + + +void OnPaint(HWND hWnd, HDC dc) +{ + RECT rec; + GetClientRect(hWnd, &rec); + cairo_surface_t* surface = cairo_win32_surface_create(dc); + cairo_t* cr = cairo_create(surface); + + cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING); + cairo_set_line_width (cr, 2.0); + + //fill background with white ... + cairo_rectangle(cr, 0, 0, rec.right, rec.bottom); + cairo_set_source_rgba(cr, 1, 1, 1, 1); + cairo_fill(cr); + + using namespace ClipperLib; + + const int scaling = 2; + + Clipper clpr; //clipper class + Polygons pg; //std::vector for polygon(s) storage + + //create a circular pattern, add the path to clipper and then draw it ... + cairo_arc(cr, 170,110,70,0,2*3.1415926); + cairo_close_path(cr); + cairo::cairo_to_clipper(cr, pg, scaling); + clpr.AddPolygons(pg, ptSubject); + cairo_set_source_rgba(cr, 0, 0, 1, 0.25); + cairo_fill_preserve (cr); + cairo_set_source_rgba(cr, 0, 0, 0, 0.5); + cairo_stroke (cr); + + //draw a star and another circle, add them to clipper and draw ... + cairo_move_to(cr, 60,110); + cairo_line_to (cr, 240,70); + cairo_line_to (cr, 110,210); + cairo_line_to (cr, 140,25); + cairo_line_to (cr, 230,200); + cairo_close_path(cr); + cairo_new_sub_path(cr); + cairo_arc(cr, 190,50,20,0,2*3.1415926); + cairo_close_path(cr); + cairo::cairo_to_clipper(cr, pg, scaling); + clpr.AddPolygons(pg, ptClip); + cairo_set_source_rgba(cr, 1, 0, 0, 0.25); + cairo_fill_preserve (cr); + cairo_set_source_rgba(cr, 0, 0, 0, 0.5); + cairo_stroke (cr); + + clpr.Execute(ctIntersection, pg, pftNonZero, pftNonZero); + //now do something fancy with the returned polygons ... + OffsetPolygons(pg, pg, offsetVal * std::pow((double)10,scaling), jtMiter); + + //finally copy the clipped path back to the cairo context and draw it ... + cairo::clipper_to_cairo(pg, cr, scaling); + cairo_set_source_rgba(cr, 1, 1, 0, 1); + cairo_fill_preserve (cr); + cairo_set_source_rgba(cr, 0, 0, 0, 1); + cairo_stroke (cr); + + cairo_text_extents_t extent; + cairo_set_font_size(cr,11); + std::stringstream ss; + ss << "Polygon offset = " << offsetVal << ". (Adjust with arrow keys)"; + std::string s = ss.str(); + cairo_text_extents(cr, s.c_str(), &extent); + cairo_move_to(cr, 10, rec.bottom - extent.height); + cairo_show_text(cr, s.c_str()); + + cairo_destroy (cr); + cairo_surface_destroy (surface); +} +//------------------------------------------------------------------------------ + +LRESULT CALLBACK WndProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + PAINTSTRUCT ps; + HDC Handle; + + switch(Msg) + { + case WM_DESTROY: + PostQuitMessage(WM_QUIT); + return 0; + + case WM_PAINT: + Handle = BeginPaint(hWnd, &ps); + OnPaint(hWnd, Handle); + EndPaint(hWnd, &ps); + return 0; + + case WM_KEYDOWN: + switch(wParam) + { + case VK_ESCAPE: + PostQuitMessage(0); + return 0; + case VK_RIGHT: + case VK_UP: + if (offsetVal < 20) offsetVal++; + InvalidateRect(hWnd, 0, false); + return 0; + case VK_LEFT: + case VK_DOWN: + if (offsetVal > -20) offsetVal--; + InvalidateRect(hWnd, 0, false); + return 0; + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + } +} +//--------------------------------------------------------------------------- + diff --git a/clipper/cpp/cpp_console/Debug/clip.txt b/clipper/cpp/cpp_console/Debug/clip.txt new file mode 100755 index 0000000..9a4a413 --- /dev/null +++ b/clipper/cpp/cpp_console/Debug/clip.txt @@ -0,0 +1,6 @@ +1 +4 +90, 10, +240, 10, +240, 90, +90, 90 \ No newline at end of file diff --git a/clipper/cpp/cpp_console/Debug/subj.txt b/clipper/cpp/cpp_console/Debug/subj.txt new file mode 100755 index 0000000..7033a4c --- /dev/null +++ b/clipper/cpp/cpp_console/Debug/subj.txt @@ -0,0 +1,13 @@ +1 +11 +0, 0, +200, 0, +200, 100, +50, 100, +50, 50, +75, 75, +100, 50, +75, 25, +50, 50, +50, 100, +0, 100, diff --git a/clipper/cpp/cpp_console/File1.cpp b/clipper/cpp/cpp_console/File1.cpp new file mode 100755 index 0000000..5c57651 --- /dev/null +++ b/clipper/cpp/cpp_console/File1.cpp @@ -0,0 +1,458 @@ +//--------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "clipper.hpp" + +//--------------------------------------------------------------------------- + +using namespace std; +using namespace ClipperLib; + +static string ColorToHtml(unsigned clr) +{ + stringstream ss; + ss << '#' << hex << std::setfill('0') << setw(6) << (clr & 0xFFFFFF); + return ss.str(); +} +//------------------------------------------------------------------------------ + +static float GetAlphaAsFrac(unsigned clr) +{ + return ((float)(clr >> 24) / 255); +} +//------------------------------------------------------------------------------ + +//a simple class that builds an SVG file with any number of polygons +class SVGBuilder +{ + + class StyleInfo + { + public: + PolyFillType pft; + unsigned brushClr; + unsigned penClr; + double penWidth; + bool showCoords; + + StyleInfo() + { + pft = pftNonZero; + brushClr = 0xFFFFFFCC; + penClr = 0xFF000000; + penWidth = 0.8; + showCoords = false; + } + }; + + class PolyInfo + { + public: + Polygons polygons; + StyleInfo si; + + PolyInfo(Polygons polygons, StyleInfo style) + { + this->polygons = polygons; + this->si = style; + } + }; + + typedef std::vector PolyInfoList; + +private: + PolyInfoList polyInfos; + static const std::string svg_xml_start[]; + static const std::string poly_end[]; + +public: + StyleInfo style; + + void AddPolygons(Polygons& poly) + { + if (poly.size() == 0) return; + polyInfos.push_back(PolyInfo(poly, style)); + } + + bool SaveToFile(char * filename, double scale = 1.0, int margin = 10) + { + //calculate the bounding rect ... + PolyInfoList::size_type i = 0; + Polygons::size_type j; + while (i < polyInfos.size()) + { + j = 0; + while (j < polyInfos[i].polygons.size() && + polyInfos[i].polygons[j].size() == 0) j++; + if (j < polyInfos[i].polygons.size()) break; + i++; + } + if (i == polyInfos.size()) return false; + + IntRect rec; + rec.left = polyInfos[i].polygons[j][0].X; + rec.right = rec.left; + rec.top = polyInfos[i].polygons[j][0].Y; + rec.bottom = rec.top; + for ( ; i < polyInfos.size(); ++i) + for (Polygons::size_type j = 0; j < polyInfos[i].polygons.size(); ++j) + for (Polygon::size_type k = 0; k < polyInfos[i].polygons[j].size(); ++k) + { + IntPoint ip = polyInfos[i].polygons[j][k]; + if (ip.X < rec.left) rec.left = ip.X; + else if (ip.X > rec.right) rec.right = ip.X; + if (ip.Y < rec.top) rec.top = ip.Y; + else if (ip.Y > rec.bottom) rec.bottom = ip.Y; + } + + if (scale == 0) scale = 1.0; + if (margin < 0) margin = 0; + rec.left = (long64)((double)rec.left * scale); + rec.top = (long64)((double)rec.top * scale); + rec.right = (long64)((double)rec.right * scale); + rec.bottom = (long64)((double)rec.bottom * scale); + long64 offsetX = -rec.left + margin; + long64 offsetY = -rec.top + margin; + + ofstream file; + file.open(filename); + if (!file.is_open()) return false; + file.setf(ios::fixed); + file.precision(0); + file << svg_xml_start[0] << + ((rec.right - rec.left) + margin*2) << "px" << svg_xml_start[1] << + ((rec.bottom - rec.top) + margin*2) << "px" << svg_xml_start[2] << + ((rec.right - rec.left) + margin*2) << " " << + ((rec.bottom - rec.top) + margin*2) << svg_xml_start[3]; + setlocale(LC_NUMERIC, "C"); + file.precision(2); + + for (PolyInfoList::size_type i = 0; i < polyInfos.size(); ++i) + { + file << " \n\n"; + for (Polygons::size_type j = 0; j < polyInfos[i].polygons.size(); ++j) + { + if (polyInfos[i].polygons[j].size() < 3) continue; + for (Polygon::size_type k = 0; k < polyInfos[i].polygons[j].size(); ++k) + { + IntPoint ip = polyInfos[i].polygons[j][k]; + file << "" << + ip.X << "," << ip.Y << "\n"; + file << "\n"; + } + } + file << "\n"; + } + } + file << "\n"; + file.close(); + setlocale(LC_NUMERIC, ""); + return true; + } +}; +//------------------------------------------------------------------------------ + +const std::string SVGBuilder::svg_xml_start [] = + {"\n" + "\n\n" + "\n\n" + }; +const std::string SVGBuilder::poly_end [] = + {"\"\n style=\"fill:", + "; fill-opacity:", + "; fill-rule:", + "; stroke:", + "; stroke-opacity:", + "; stroke-width:", + ";\"/>\n\n" + }; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +inline long64 Round(double val) +{ + if ((val < 0)) return (long64)(val - 0.5); else return (long64)(val + 0.5); +} +//------------------------------------------------------------------------------ + +bool LoadFromFile(Polygons &ppg, char * filename, double scale= 1, + int xOffset = 0, int yOffset = 0) +{ + ppg.clear(); + + FILE *f = fopen(filename, "r"); + if (!f) return false; + int polyCnt, vertCnt; + char junk [80]; + double X, Y; + if (fscanf(f, "%d", &polyCnt) == 1 && polyCnt > 0) + { + ppg.resize(polyCnt); + for (int i = 0; i < polyCnt; i++) { + if (fscanf(f, "%d", &vertCnt) != 1 || vertCnt <= 0) break; + ppg[i].resize(vertCnt); + for (int j = 0; j < vertCnt; j++) { + if (fscanf(f, "%lf%*[, ]%lf", &X, &Y) != 2) break; + ppg[i][j].X = Round((X + xOffset) * scale); + ppg[i][j].Y = Round((Y + yOffset) * scale); + fgets(junk, 80, f); + } + } + } + fclose(f); + return true; +} +//------------------------------------------------------------------------------ + +void SaveToConsole(const string name, const Polygons &pp, double scale = 1.0) +{ + cout << '\n' << name << ":\n" + << pp.size() << '\n'; + for (unsigned i = 0; i < pp.size(); ++i) + { + cout << pp[i].size() << '\n'; + for (unsigned j = 0; j < pp[i].size(); ++j) + cout << pp[i][j].X /scale << ", " << pp[i][j].Y /scale << ",\n"; + } + cout << "\n"; +} +//--------------------------------------------------------------------------- + +void SaveToFile(char *filename, Polygons &pp, double scale = 1) +{ + FILE *f = fopen(filename, "w"); + if (!f) return; + fprintf(f, "%d\n", pp.size()); + for (unsigned i = 0; i < pp.size(); ++i) + { + fprintf(f, "%d\n", pp[i].size()); + if (scale > 1.01 || scale < 0.99) { + for (unsigned j = 0; j < pp[i].size(); ++j) + fprintf(f, "%.6lf, %.6lf,\n", + (double)pp[i][j].X /scale, (double)pp[i][j].Y /scale); + } + else + { + for (unsigned j = 0; j < pp[i].size(); ++j) + fprintf(f, "%lld, %lld,\n", pp[i][j].X, pp[i][j].Y ); + } + } + fclose(f); +} +//--------------------------------------------------------------------------- + +void MakeRandomPoly(int edgeCount, int width, int height, Polygons & poly) +{ + poly.resize(1); + poly[0].resize(edgeCount); + for (int i = 0; i < edgeCount; i++){ + poly[0][i].X = rand() % width; + poly[0][i].Y = rand() % height; + } +} +//------------------------------------------------------------------------------ + +#pragma argsused +int _tmain(int argc, _TCHAR* argv[]) +{ + if (argc > 1 && + (strcmp(argv[1], "-b") == 0 || strcmp(argv[1], "--benchmark") == 0)) + { + //do a benchmark test that creates a subject and a clip polygon both with + //100 vertices randomly placed in a 400 * 400 space. Then perform an + //intersection operation based on even-odd filling. Repeat all this X times. + int loop_cnt = 100; + char * dummy; + if (argc > 2) loop_cnt = strtol(argv[2], &dummy, 10); + if (loop_cnt == 0) loop_cnt = 100; + cout << "\nPerforming " << loop_cnt << " random intersection operations ... "; + srand(time(0)); + int error_cnt = 0; + Polygons subject, clip, solution; + Clipper clpr; + time_t time_start = clock(); + for (int i = 0; i < loop_cnt; i++) { + MakeRandomPoly(100, 400, 400, subject); + MakeRandomPoly(100, 400, 400, clip); + clpr.Clear(); + clpr.AddPolygons(subject, ptSubject); + clpr.AddPolygons(clip, ptClip); + if (!clpr.Execute(ctIntersection, solution, pftEvenOdd, pftEvenOdd)) + error_cnt++; + } + double time_elapsed = double(clock() - time_start)/CLOCKS_PER_SEC; + cout << "\nFinished in " << time_elapsed << " secs with "; + cout << error_cnt << " errors.\n\n"; + //let's save the very last result ... + SaveToFile("Subject.txt", subject); + SaveToFile("Clip.txt", clip); + SaveToFile("Solution.txt", solution); + //and see it as an image too ... + SVGBuilder svg; + svg.style.penWidth = 0.8; + svg.style.pft = pftEvenOdd; + svg.style.brushClr = 0x1200009C; + svg.style.penClr = 0xCCD3D3DA; + svg.AddPolygons(subject); + svg.style.brushClr = 0x129C0000; + svg.style.penClr = 0xCCFFA07A; + svg.AddPolygons(clip); + svg.style.brushClr = 0x6080ff9C; + svg.style.penClr = 0xFF003300; + svg.style.pft = pftNonZero; + svg.AddPolygons(solution); + svg.SaveToFile("solution.svg"); + return 0; + } + + if (argc < 3) + { + cout << "\nUSAGE:\n" + << "clipper --benchmark [LOOP_COUNT (default = 100)]\n" + << "or\n" + << "clipper sub_file clp_file CLIPTYPE [SUB_FILL CLP_FILL] [PRECISION] [SVG_SCALE]\n" + << "where ...\n" + << " CLIPTYPE = INTERSECTION or UNION or DIFFERENCE or XOR, and\n" + << " ???_FILL = EVENODD or NONZERO (default = NONZERO)\n" + << " PRECISION = in decimal places (default = 0)\n" + << " SVG_SCALE = SVG output scale (default = 1.0)\n\n"; + cout << "\nINPUT AND OUTPUT FILE FORMAT ([optional] {comments}):\n" + << "Polygon Count\n" + << "Vertex Count {first polygon}\n" + << "X, Y[,] {first vertex}\n" + << "X, Y[,] {next vertex}\n" + << "{etc.}\n" + << "Vertex Count {second polygon, if there is one}\n" + << "X, Y[,] {first vertex of second polygon}\n" + << "{etc.}\n\n"; + return 1; + } + + int scale_log10 = 0; + char * dummy; + if (argc > 6) scale_log10 = strtol(argv[6], &dummy, 10); + double scale = std::pow(double(10), scale_log10); + + double svg_scale = 1.0; + if (argc > 7) svg_scale = strtod(argv[7], &dummy); + svg_scale /= scale; + + Polygons subject, clip; + + if (!LoadFromFile(subject, argv[1], scale)) + { + cerr << "\nCan't open the file " << argv[1] + << " or the file format is invalid.\n"; + return 1; + } + if (!LoadFromFile(clip, argv[2], scale)) + { + cerr << "\nCan't open the file " << argv[2] + << " or the file format is invalid.\n"; + return 1; + } + + ClipType clipType = ctIntersection; + const string sClipType[] = {"INTERSECTION", "UNION", "DIFFERENCE", "XOR"}; + + if (argc > 3) + { + if (stricmp(argv[3], "XOR") == 0) clipType = ctXor; + else if (stricmp(argv[3], "UNION") == 0) clipType = ctUnion; + else if (stricmp(argv[3], "DIFFERENCE") == 0) clipType = ctDifference; + else clipType = ctIntersection; + } + + PolyFillType subj_pft = pftNonZero, clip_pft = pftNonZero; + if (argc > 5) + { + if (stricmp(argv[4], "EVENODD") == 0) subj_pft = pftEvenOdd; + if (stricmp(argv[5], "EVENODD") == 0) clip_pft = pftEvenOdd; + } + + Clipper c; + c.AddPolygons(subject, ptSubject); + c.AddPolygons(clip, ptClip); + Polygons solution; + + bool succeeded = c.Execute(clipType, solution, subj_pft, clip_pft); + string s = "Subjects ("; + s += (subj_pft == pftEvenOdd ? "EVENODD)" : "NONZERO)"); + + //ie don't change the polygons back to the original size if we've + //just down-sized them to a manageable (all-in-one-screen) size ... + //if (scale < 1) scale = 1; + + SaveToConsole(s, subject, scale); + s = "Clips ("; + s += (clip_pft == pftEvenOdd ? "EVENODD)" : "NONZERO)"); + SaveToConsole(s, clip, scale); + if (succeeded) { + s = "Solution (using " + sClipType[clipType] + ")"; + //SaveToConsole(s, solution, scale); + SaveToFile("solution.txt", solution, scale); + + //OffsetPolygons(solution, solution, -5.0 *scale, jtRound, 4); + + //let's see the result too ... + SVGBuilder svg; + svg.style.penWidth = 0.8; + svg.style.brushClr = 0x1200009C; + svg.style.penClr = 0xCCD3D3DA; + svg.style.pft = subj_pft; + svg.AddPolygons(subject); + svg.style.brushClr = 0x129C0000; + svg.style.penClr = 0xCCFFA07A; + svg.style.pft = clip_pft; + svg.AddPolygons(clip); + svg.style.brushClr = 0x6080ff9C; + svg.style.penClr = 0xFF003300; + svg.style.pft = pftNonZero; + svg.AddPolygons(solution); + svg.SaveToFile("solution.svg", svg_scale); + } else + cout << (sClipType[clipType] + " failed!\n\n"); + + return 0; +} +//--------------------------------------------------------------------------- diff --git a/clipper/cpp/cpp_console/clipper.cbproj b/clipper/cpp/cpp_console/clipper.cbproj new file mode 100755 index 0000000..18a198d --- /dev/null +++ b/clipper/cpp/cpp_console/clipper.cbproj @@ -0,0 +1,147 @@ + + + {13421A08-E1A3-411C-B53C-144521F7116E} + 12.0 + Debug + + + true + + + true + Base + true + + + true + Base + true + + + false + true + rtl.lib + rtl.lib + vcl.bpi;rtl.bpi;vclx.bpi;vclactnband.bpi;dbrtl.bpi;vcldb.bpi;bdertl.bpi;vcldbx.bpi;dsnap.bpi;dsnapcon.bpi;TeeUI.bpi;Tee.bpi;TeeDB.bpi;adortl.bpi;IndyCore.bpi;IndySystem.bpi;IndyProtocols.bpi;VclSmp.bpi;dbexpress.bpi;DbxCommonDriver.bpi;DataSnapIndy10ServerTransport.bpi;DataSnapProviderClient.bpi;DataSnapServer.bpi;DbxClientDriver.bpi;DBXInterBaseDriver.bpi;DBXMySQLDriver.bpi;dbxcds.bpi;DBXSybaseASEDriver.bpi;DBXSybaseASADriver.bpi;DBXOracleDriver.bpi;DBXMSSQLDriver.bpi;DBXInformixDriver.bpi;DBXDb2Driver.bpi;bcbie.bpi;xmlrtl.bpi;bcbsmp.bpi;GR32_CB6.bpi;GR32_DSGN_CB6.bpi + true + exe + CppConsoleApplication + JPHNE + NO_STRICT + true + ..\..\cpp;C:\Program Files (x86)\Borland\graphics32-1-9-0-r1336\Examples\Vcl\Drawing\Clipper\cpp\test_console;$(CG_BOOST_ROOT)\boost\tr1\tr1;$(BDS)\include;$(BDS)\include\dinkumware;$(BDS)\include\vcl;$(CG_BOOST_ROOT) + ..\..\cpp;C:\Program Files (x86)\Borland\graphics32-1-9-0-r1336\Examples\Vcl\Drawing\Clipper\cpp\test_console;$(BDS)\lib;$(BDS)\lib\obj;$(BDS)\lib\psdk + false + true + + + false + false + true + false + true + _DEBUG;$(Defines) + false + Debug + true + None + DEBUG + true + true + true + $(BDS)\lib\debug;$(ILINK_LibraryPath) + true + Full + true + + + NDEBUG;$(Defines) + Release + $(BDS)\lib\release;$(ILINK_LibraryPath) + None + + + + 2 + + + 1 + + + 1 + + + Base + + + Cfg_2 + Base + + + Cfg_1 + Base + + + + + CPlusPlusBuilder.Personality.12 + CppConsoleApplication + + + + False + False + 1 + 0 + 0 + 0 + False + False + False + False + False + 3081 + 1252 + + + + + 1.0.0.0 + + + + + + 1.0.0.0 + + + + + + + subj.txt clip.txt intersection NONZERO NONZERO 0 1.0 + + False + + + + + + + False + + False + + True + False + + + False + True + True + + + + 12 + + diff --git a/clipper/cpp/cpp_opengl/clipper_demo.exe b/clipper/cpp/cpp_opengl/clipper_demo.exe new file mode 100755 index 0000000..9eba464 Binary files /dev/null and b/clipper/cpp/cpp_opengl/clipper_demo.exe differ diff --git a/clipper/cpp/cpp_opengl/clipper_demo.sln b/clipper/cpp/cpp_opengl/clipper_demo.sln new file mode 100755 index 0000000..56899d2 --- /dev/null +++ b/clipper/cpp/cpp_opengl/clipper_demo.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual C++ Express 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "clipper_demo", "clipper_demo.vcxproj", "{4D48A928-F288-40F9-9FA0-4AC650602636}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4D48A928-F288-40F9-9FA0-4AC650602636}.Debug|Win32.ActiveCfg = Debug|Win32 + {4D48A928-F288-40F9-9FA0-4AC650602636}.Debug|Win32.Build.0 = Debug|Win32 + {4D48A928-F288-40F9-9FA0-4AC650602636}.Release|Win32.ActiveCfg = Release|Win32 + {4D48A928-F288-40F9-9FA0-4AC650602636}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/clipper/cpp/cpp_opengl/clipper_demo.vcxproj b/clipper/cpp/cpp_opengl/clipper_demo.vcxproj new file mode 100755 index 0000000..dfadf98 --- /dev/null +++ b/clipper/cpp/cpp_opengl/clipper_demo.vcxproj @@ -0,0 +1,95 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {4D48A928-F288-40F9-9FA0-4AC650602636} + Win32Proj + opengl_app + clipper_demo + + + + Application + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + true + $(SourcePath) + $(IncludePath) + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + Windows + true + opengl32.lib;glu32.lib;comctl32.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreaded + + + Windows + true + true + true + opengl32.lib;glu32.lib;comctl32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/clipper/cpp/cpp_opengl/icon.res b/clipper/cpp/cpp_opengl/icon.res new file mode 100755 index 0000000..a4fc1a7 Binary files /dev/null and b/clipper/cpp/cpp_opengl/icon.res differ diff --git a/clipper/cpp/cpp_opengl/main.cpp b/clipper/cpp/cpp_opengl/main.cpp new file mode 100755 index 0000000..bc80630 --- /dev/null +++ b/clipper/cpp/cpp_opengl/main.cpp @@ -0,0 +1,645 @@ +#include +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include "../clipper.hpp" + +using namespace std; +using namespace ClipperLib; + +enum poly_color_type { pctSubject, pctClip, pctSolution }; + +//global vars ... +HWND hWnd; +HWND hStatus; +HDC hDC; +HGLRC hRC; +ClipType ct = ctIntersection; +PolyFillType pft = pftEvenOdd; +JoinType jt = jtRound; +bool show_clipping = true; +Polygons sub, clp, sol; +int VertCount = 5; +int scale = 10; +double delta = 0.0; + +typedef std::vector< GLdouble* > Vectors; +Vectors vectors; + +//------------------------------------------------------------------------------ +// heap memory management for GLUtesselator ... +//------------------------------------------------------------------------------ + +GLdouble* NewVector(GLdouble x, GLdouble y) +{ + GLdouble *vert = new GLdouble[3]; + vert[0] = x; + vert[1] = y; + vert[2] = 0; + vectors.push_back(vert); + return vert; +} +//------------------------------------------------------------------------------ + +void ClearVectors() +{ + for (Vectors::size_type i = 0; i < vectors.size(); ++i) + delete[] vectors[i]; + vectors.clear(); +} + +//------------------------------------------------------------------------------ +// GLUtesselator callback functions ... +//------------------------------------------------------------------------------ + +void CALLBACK BeginCallback(GLenum type) +{ + glBegin(type); +} +//------------------------------------------------------------------------------ + +void CALLBACK EndCallback() +{ + glEnd(); +} +//------------------------------------------------------------------------------ + +void CALLBACK VertexCallback(GLvoid *vertex) +{ + glVertex3dv( (const double *)vertex ); +} +//------------------------------------------------------------------------------ + +void CALLBACK CombineCallback(GLdouble coords[3], + GLdouble *data[4], GLfloat weight[4], GLdouble **dataOut ) +{ + GLdouble *vert = NewVector(coords[0], coords[1]); + *dataOut = vert; +} +//------------------------------------------------------------------------------ + +wstring str2wstr(const std::string &s) { + int slength = (int)s.length() + 1; + int len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0); + wchar_t* buf = new wchar_t[len]; + MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len); + std::wstring r(buf); + delete[] buf; + return r; +} +//------------------------------------------------------------------------------ + +void CALLBACK ErrorCallback(GLenum errorCode) +{ + std::wstring s = str2wstr( (char *)gluErrorString(errorCode) ); + SetWindowText(hWnd, s.c_str()); +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +// Set up pixel format for graphics initialization +void SetupPixelFormat() +{ + PIXELFORMATDESCRIPTOR pfd; + ZeroMemory( &pfd, sizeof(pfd)); + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 32; + int pfIdx = ChoosePixelFormat(hDC, &pfd); + if (pfIdx != 0) SetPixelFormat(hDC, pfIdx, &pfd); +} +//------------------------------------------------------------------------------ + +// Initialize OpenGL graphics +void InitGraphics() +{ + hDC = GetDC(hWnd); + SetupPixelFormat(); + hRC = wglCreateContext(hDC); + wglMakeCurrent(hDC, hRC); + glDisable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glTranslatef (0.375, 0.375, 0); +} +//------------------------------------------------------------------------------ + +void MakeRandomPoly(ClipperLib::Polygon &p, int width, int height, int edgeCount) +{ + p.resize(edgeCount); + for (int i = 0; i < edgeCount; i++) + { + p[i].X = (rand()%(width -20) +10)*scale; + p[i].Y = (rand()%(height -20) +10)*scale; + } +} +//------------------------------------------------------------------------------ + +void ResizeGraphics(int width, int height) +{ + //setup 2D projection with origin at top-left corner ... + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, width, height, 0, 0, 1); + glViewport(0, 0, width, height); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} +//------------------------------------------------------------------------------ + +void DrawPolygon(Polygons &pgs, poly_color_type pct) +{ + switch (pct) + { + case pctSubject: glColor4f(0.0f, 0.0f, 1.0f, 0.062f); break; + case pctClip: glColor4f(1.0f, 1.0f, 0.0f, 0.062f); break; + default: glColor4f(0.0f, 1.0f, 0.0f, 0.25f); + } + + GLUtesselator* tess = gluNewTess(); + gluTessCallback(tess, GLU_TESS_BEGIN, (void (CALLBACK*)())&BeginCallback); + gluTessCallback(tess, GLU_TESS_VERTEX, (void (CALLBACK*)())&VertexCallback); + gluTessCallback(tess, GLU_TESS_END, (void (CALLBACK*)())&EndCallback); + gluTessCallback(tess, GLU_TESS_COMBINE, (void (CALLBACK*)())&CombineCallback); + gluTessCallback(tess, GLU_TESS_ERROR, (void (CALLBACK*)())&ErrorCallback); + gluTessNormal(tess, 0.0, 0.0, 1.0); + + switch (pft) + { + case pftEvenOdd: + gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); + break; + case pftNonZero: + gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO); + break; + case pftPositive: + gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE); + break; + default: //case pftNegative + if (pct == pctSolution) + gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO); + else + gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NEGATIVE); + } + + gluTessProperty(tess, GLU_TESS_BOUNDARY_ONLY, GL_FALSE); //GL_FALSE + gluTessBeginPolygon(tess, NULL); + for (Polygons::size_type i = 0; i < pgs.size(); ++i) + { + gluTessBeginContour(tess); + for (ClipperLib::Polygon::size_type j = 0; j < pgs[i].size(); ++j) + { + GLdouble *vert = + NewVector((GLdouble)pgs[i][j].X/scale, (GLdouble)pgs[i][j].Y/scale); + gluTessVertex(tess, vert, vert); + } + gluTessEndContour(tess); + } + gluTessEndPolygon(tess); + ClearVectors(); + + switch (pct) + { + case pctSubject: + glColor4f(0.0f, 0.6f, 1.0f, 0.5f); + break; + case pctClip: + glColor4f(1.0f, 0.6f, 0.0f, 0.5f); + break; + default: + glColor4f(0.0f, 0.4f, 0.0f, 1.0f); + } + if (pct == pctSolution) glLineWidth(1.0f); else glLineWidth(0.8f); + + gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); + gluTessProperty(tess, GLU_TESS_BOUNDARY_ONLY, GL_TRUE); + for (Polygons::size_type i = 0; i < pgs.size(); ++i) + { + gluTessBeginPolygon(tess, NULL); + gluTessBeginContour(tess); + for (ClipperLib::Polygon::size_type j = 0; j < pgs[i].size(); ++j) + { + GLdouble *vert = + NewVector((GLdouble)pgs[i][j].X/scale, (GLdouble)pgs[i][j].Y/scale); + gluTessVertex(tess, vert, vert); + } + + switch (pct) + { + case pctSubject: + glColor4f(0.0f, 0.0f, 0.8f, 0.5f); + break; + case pctClip: + glColor4f(0.6f, 0.0f, 0.0f, 0.5f); + } + gluTessEndContour(tess); + gluTessEndPolygon(tess); + } + + //final cleanup ... + gluDeleteTess(tess); + ClearVectors(); +} +//------------------------------------------------------------------------------ + +void DrawGraphics() +{ + //this can take a few moments ... + HCURSOR hWaitCursor = LoadCursor(NULL, IDC_WAIT); + SetCursor(hWaitCursor); + SetClassLong(hWnd, GCL_HCURSOR, (DWORD)hWaitCursor); + + //fill background with a light off-gray color ... + glClearColor(1,1,1,1); + glClear(GL_COLOR_BUFFER_BIT); + + //glRasterPos2f(110, 340); + //glColor4f(0.0f, 1.0f, 0.0f, 1.0f); + //char * text = "Positive Fills"; + //for (int i = 0; i < strlen(text); ++i) + // glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, text[i]); + + DrawPolygon(sub, pctSubject); + DrawPolygon(clp, pctClip); + if (show_clipping) DrawPolygon(sol, pctSolution); + + wstringstream ss; + if (!show_clipping) + ss << L"Clipper Demo - NO CLIPPING"; + else + switch (ct) + { + case ctUnion: + ss << L"Clipper Demo - UNION"; + break; + case ctDifference: + ss << L"Clipper Demo - DIFFERENCE"; + break; + case ctXor: + ss << L"Clipper Demo - XOR"; + break; + default: + ss << L"Clipper Demo - INTERSECTION"; + } + + switch(pft) + { + case pftEvenOdd: + ss << L" (EvenOdd filled polygons with "; + break; + case pftNonZero: + ss << L" (NonZero filled polygons with "; + break; + case pftPositive: + ss << L" (Positive filled polygons with "; + break; + default: + ss << L" (Negative filled polygons with "; + } + ss << VertCount << " vertices each.)"; + SetWindowText(hWnd, ss.str().c_str()); + + HCURSOR hArrowCursor = LoadCursor(NULL, IDC_ARROW); + SetCursor(hArrowCursor); + SetClassLong(hWnd, GCL_HCURSOR, (DWORD)hArrowCursor); +} +//------------------------------------------------------------------------------ + +inline long64 Round(double val) +{ + if ((val < 0)) return (long64)(val - 0.5); else return (long64)(val + 0.5); +} +//------------------------------------------------------------------------------ + +//bool LoadFromFile(Polygons &ppg, char * filename, double scale= 1, +// int xOffset = 0, int yOffset = 0) +//{ +// ppg.clear(); +// ifstream infile(filename); +// if (!infile.is_open()) return false; +// int polyCnt, vertCnt; +// double X, Y; +// +// infile >> polyCnt; +// infile.ignore(80, '\n'); +// if (infile.good() && polyCnt > 0) +// { +// ppg.resize(polyCnt); +// for (int i = 0; i < polyCnt; i++) +// { +// infile >> vertCnt; +// infile.ignore(80, '\n'); +// if (!infile.good() || vertCnt < 0) break; +// ppg[i].resize(vertCnt); +// for (int j = 0; infile.good() && j < vertCnt; j++) +// { +// infile >> X; +// while (infile.peek() == ' ') infile.ignore(); +// if (infile.peek() == ',') infile.ignore(); +// while (infile.peek() == ' ') infile.ignore(); +// infile >> Y; +// ppg[i][j].X = Round((X + xOffset) * scale); +// ppg[i][j].Y = Round((Y + yOffset) * scale); +// infile.ignore(80, '\n'); +// } +// } +// } +// infile.close(); +// return true; +//} +//------------------------------------------------------------------------------ + +//void SaveToFile(const char *filename, Polygons &pp, double scale = 1) +//{ +// ofstream of(filename); +// if (!of.is_open()) return; +// of << pp.size() << "\n"; +// for (Polygons::size_type i = 0; i < pp.size(); ++i) +// { +// of << pp[i].size() << "\n"; +// if (scale > 1.01 || scale < 0.99) +// of << fixed << setprecision(6); +// for (Polygon::size_type j = 0; j < pp[i].size(); ++j) +// of << (double)pp[i][j].X /scale << ", " << (double)pp[i][j].Y /scale << ",\n"; +// } +// of.close(); +//} +//--------------------------------------------------------------------------- + +void MakePolygonFromInts(int *ints, int size, ClipperLib::Polygon &p) +{ + p.clear(); + p.reserve(size / 2); + for (int i = 0; i < size; i +=2) + p.push_back(IntPoint(ints[i], ints[i+1])); +} +//--------------------------------------------------------------------------- + +void TranslatePolygon(ClipperLib::Polygon &p, int dx, int dy) +{ + for (size_t i = 0; i < p.size(); ++i) + { + p[i].X += dx; + p[i].Y += dy; + } +} +//--------------------------------------------------------------------------- + +//void MakePolygonFromIntArray(const int ints[][2], int size, ClipperLib::Polygon &p) +//{ +// p.clear(); +// p.reserve(size / 2); +// for (int i = 0; i < size; ++i) +// p.push_back(IntPoint(ints[i][0], ints[i][1])); +//} +//--------------------------------------------------------------------------- + +void UpdatePolygons(bool updateSolutionOnly) +{ + if (VertCount < 0) VertCount = -VertCount; + if (VertCount > 50) VertCount = 50; + if (VertCount < 3) VertCount = 3; + + Clipper c; + if (!updateSolutionOnly) + { + delta = 0.0; + + RECT r; + GetWindowRect(hStatus, &r); + int statusHeight = r.bottom - r.top; + GetClientRect(hWnd, &r); + + sub.resize(1); + clp.resize(1); + + //int ints[] = {0,0,0, -100,100, -100,100, 0}; + //int ints2[] = {0, 100, 100, -200,0, -200,100, 100}; + //MakePolygonFromInts(ints, 8, sub[0]); + //TranslatePolygon(sub[0], 100,220); + //MakePolygonFromInts(ints2, 8, clp[0]); + //TranslatePolygon(sub[1], 100,220); + MakeRandomPoly(sub[0], r.right, r.bottom - statusHeight, VertCount); + MakeRandomPoly(clp[0], r.right, r.bottom - statusHeight, VertCount); + + //SaveToFile("subj.txt", sub); + //SaveToFile("clip.txt", clp); + } + + c.AddPolygons(sub, ptSubject); + c.AddPolygons(clp, ptClip); + c.Execute(ct, sol, pft, pft); + if (delta != 0.0) + OffsetPolygons(sol,sol,delta,jt); + + InvalidateRect(hWnd, NULL, false); +} +//------------------------------------------------------------------------------ + +void DoNumericKeyPress(int num) +{ + if (VertCount >= 0) VertCount = -num; + else if (VertCount > -10) VertCount = VertCount*10 - num; + else Beep(1000, 100); +} +//------------------------------------------------------------------------------ + +LONG WINAPI MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + int clientwidth, clientheight; + switch (uMsg) + { + + case WM_SIZE: + clientwidth = LOWORD(lParam); + clientheight = HIWORD(lParam); + ResizeGraphics(clientwidth, clientheight); + SetWindowPos(hStatus, NULL, 0, + clientheight, clientwidth, 0, SWP_NOACTIVATE | SWP_NOZORDER); + return 0; + + case WM_PAINT: + HDC hdc; + PAINTSTRUCT ps; + hdc = BeginPaint(hWnd, &ps); + //do the drawing ... + DrawGraphics(); + SwapBuffers(hdc); + EndPaint(hWnd, &ps); + return 0; + + case WM_CLOSE: + DestroyWindow(hWnd); + return 0; + + case WM_DESTROY: + PostQuitMessage(0); + return 0; + + case WM_HELP: + MessageBox(hWnd, + L"Clipper Demo tips...\n\n" + L"I - for Intersection operations.\n" + L"U - for Union operations.\n" + L"D - for Difference operations.\n" + L"X - for XOR operations.\n" + L"------------------------------\n" + L"Q - Toggle clipping on/off.\n" + L"------------------------------\n" + L"E - for EvenOdd fills.\n" + L"Z - for NonZero fills.\n" + L"P - for Positive fills.\n" + L"N - for Negative fills.\n" + L"------------------------------\n" + L"nn - number of vertices (3..50).\n" + L"------------------------------\n" + L"UP arrow - Expand Solution.\n" + L"DN arrow - Contract Solution.\n" + L"LT or RT arrow - Reset Solution.\n" + L"------------------------------\n" + L"M - Miter OffsetPolygons.\n" + L"S - Square OffsetPolygons.\n" + L"R - Round OffsetPolygons.\n" + L"------------------------------\n" + L"SPACE, ENTER or click to refresh.\n" + L"F1 - to see this help dialog again.\n" + L"Esc - to quit.\n", + L"Clipper Demo - Help", 0); + return 0; + + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case 1: PostQuitMessage(0); break; //escape + case 98: SendMessage(hWnd, WM_HELP, 0, 0); break; + case 99: MessageBox(hWnd, L"After closing this dialog,\ntype the required number of vertices (3-50) then ...", L"Clipper Demo", 0); + case 101: show_clipping = true; ct = ctIntersection; UpdatePolygons(true); break; + case 102: show_clipping = true; ct = ctUnion; UpdatePolygons(true); break; + case 103: show_clipping = true; ct = ctDifference; UpdatePolygons(true); break; + case 104: show_clipping = true; ct = ctXor; UpdatePolygons(true); break; + case 105: pft = pftEvenOdd; UpdatePolygons(true); break; + case 106: pft = pftNonZero; UpdatePolygons(true); break; + case 107: pft = pftPositive; UpdatePolygons(true); break; + case 108: pft = pftNegative; UpdatePolygons(true); break; + case 109: show_clipping = !show_clipping; UpdatePolygons(true); break; + case 110: case 111: case 112: case 113: case 114: + case 115: case 116: case 117: case 118: case 119: + DoNumericKeyPress(LOWORD(wParam) - 110); + break; + case 120: UpdatePolygons(false); break; //space, return + case 131: if (delta < 20*scale) {delta += scale; UpdatePolygons(true);} break; + case 132: if (delta > -20*scale) {delta -= scale; UpdatePolygons(true);} break; + case 133: if (delta != 0.0) {delta = 0.0; UpdatePolygons(true);} break; + case 141: {jt = jtMiter; if (delta != 0.0) UpdatePolygons(true);} break; + case 142: {jt = jtSquare; if (delta != 0.0) UpdatePolygons(true);} break; + case 143: {jt = jtRound; if (delta != 0.0) UpdatePolygons(true);} break; + default: return DefWindowProc (hWnd, uMsg, wParam, lParam); + } + return 0; + + case WM_LBUTTONUP: + UpdatePolygons(false); + return 0; + + // Default event handler + default: return DefWindowProc (hWnd, uMsg, wParam, lParam); + } +} +//------------------------------------------------------------------------------ + +int WINAPI WinMain (HINSTANCE hInstance, + HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + + const LPCWSTR appname = TEXT("Clipper Demo"); + + WNDCLASS wndclass; + MSG msg; + + // Define the window class + wndclass.style = 0; + wndclass.lpfnWndProc = (WNDPROC)MainWndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = hInstance; + wndclass.hIcon = LoadIcon(hInstance, appname); + wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); + wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wndclass.lpszMenuName = appname; + wndclass.lpszClassName = appname; + + // Register the window class + if (!RegisterClass(&wndclass)) return FALSE; + + HMENU menu = LoadMenu(hInstance, MAKEINTRESOURCE(1)); + HACCEL accel = LoadAccelerators(hInstance, MAKEINTRESOURCE(1)); + + int windowSizeX = 540, windowSizeY = 400; + + int dx = GetSystemMetrics(SM_XVIRTUALSCREEN); + int dy = GetSystemMetrics(SM_YVIRTUALSCREEN); + dx += ((GetSystemMetrics(SM_CXSCREEN) -windowSizeX) /2); + dy += ((GetSystemMetrics(SM_CYSCREEN) -windowSizeY) /2); + + // Create the window + hWnd = CreateWindow( + appname, + appname, + WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, + dx, dy, windowSizeX, windowSizeY, + NULL, + menu, + hInstance, + NULL); + + if (!hWnd) return FALSE; + + //replace the main window icon with Resource Icon #1 ... + HANDLE small_ico = LoadImage(hInstance, MAKEINTRESOURCE(1), IMAGE_ICON, 16, 16, 0); + HANDLE big_ico = LoadImage(hInstance, MAKEINTRESOURCE(1), IMAGE_ICON, 32, 32, 0); + SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)small_ico); + SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)big_ico); + + InitCommonControls(); + hStatus = CreateWindowEx(0, L"msctls_statusbar32", NULL, WS_CHILD | WS_VISIBLE, + 0, 0, 0, 0, hWnd, (HMENU)0, hInstance, NULL); + SetWindowText(hStatus, L" Copyright © Angus Johnson 2011"); + + // Initialize OpenGL + InitGraphics(); + + srand((unsigned)time(0)); + UpdatePolygons(false); + + // Display the window + ShowWindow(hWnd, nCmdShow); + UpdateWindow(hWnd); + + // Event loop + while (true) + { + if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) == TRUE) + { + if (!GetMessage(&msg, NULL, 0, 0)) return TRUE; + + if (!TranslateAccelerator(hWnd, accel, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + wglMakeCurrent(NULL, NULL); + wglDeleteContext(hRC); + ReleaseDC(hWnd, hDC); +} +//------------------------------------------------------------------------------ diff --git a/clipper/cpp/cpp_opengl/menu.res b/clipper/cpp/cpp_opengl/menu.res new file mode 100755 index 0000000..b639bb8 Binary files /dev/null and b/clipper/cpp/cpp_opengl/menu.res differ diff --git a/clipper/greedySelectDetrsCoveragemex.cpp b/clipper/greedySelectDetrsCoveragemex.cpp new file mode 100755 index 0000000..ec58ef8 --- /dev/null +++ b/clipper/greedySelectDetrsCoveragemex.cpp @@ -0,0 +1,190 @@ +#include "mex.h" +#include "clipper.hpp" + +using namespace ClipperLib; +using namespace std; + +double PsArea(Polygons & ps){ + double resarea=0; + for(int i = 0; iresize(1); + (*rect)[0].resize(4); + //mexPrintf("4\n"); + (*rect)[0][0].X=(long64)rects[i]; + (*rect)[0][0].Y=(long64)rects[i+1*len]; + (*rect)[0][1].X=(long64)rects[i]; + (*rect)[0][1].Y=(long64)rects[i+3*len]; + (*rect)[0][2].X=(long64)rects[i+2*len]; + (*rect)[0][2].Y=(long64)rects[i+3*len]; + (*rect)[0][3].X=(long64)rects[i+2*len]; + (*rect)[0][3].Y=(long64)rects[i+1*len]; + //mexPrintf("3\n"); + if(i==starti){ + solution_old=rect; + }else{ + Clipper c; + c.AddPolygons(*solution_old,ptSubject); + c.AddPolygons(*rect,ptClip); + solution = new Polygons(); + c.Execute(ctUnion,*solution,pftNonZero,pftNonZero); + //mexPrintf("arean %f\n",total); + delete solution_old; + delete rect; + solution_old=solution; + } + } + return solution_old; +} + +void mexFunction(int nlhs, mxArray *plhs[], + int nrhs, const mxArray *prhs[]) +{ + mexPrintf("entered mexfn\n"); + mexEvalString("drawnow;"); + long64* rects=((long64*)mxGetPr(prhs[0])); + int len = *(mxGetDimensions(prhs[0])); + long64* ntofind=((long64*)mxGetPr(prhs[1])); + //mexPrintf("len %d\n",len); + int out[1]; + out[0] = (int) (*ntofind); + mexPrintf("outsize:%d\n",out[0]); + mexEvalString("drawnow;"); + plhs[0]=mxCreateNumericArray(1, out, mxDOUBLE_CLASS, mxREAL); + plhs[1]=mxCreateNumericArray(1, out, mxDOUBLE_CLASS, mxREAL); + double* outdata=((double*)mxGetPr(plhs[0])); + double* outscores=((double*)mxGetPr(plhs[1])); + int maximg=0; + int maxdetr=0; + for(int i=0; imaxdetr) maxdetr=(long64)rects[i+4*len]; + if(rects[i+5*len]>maximg) maximg=(long64)rects[i+5*len]; + } + vector*> allpolys(maxdetr+1); + for(int i = 0; i<=maxdetr; i++){ + allpolys[i] = new vector(maximg+1); + } + int i = 0; + mexPrintf("generating polys_new\n"); + mexEvalString("drawnow;"); + while(i totalpolys(maximg+1); + vector tprequiresdelete(maximg+1,false); + vector prevcontribution(maxdetr+1,1.0/0.0); + mexPrintf("computing unions\n"); + mexEvalString("drawnow;"); + double totalcoverage=0; + for(int i = 0; i<*ntofind; ++i){ + int bestdetr=0; + double bestarea=0; + vector bestunionpolys(maximg+1); + vector buprequiresdelete(maximg+1,false); + vector bupcamefromtp(maximg+1,false); + for(int curdetr=1; curdetr<=maxdetr; curdetr++){ + if(prevcontribution[curdetr] tmpunionpolys(maximg+1); + vector tuprequiresdelete(maximg+1,false); + vector tupcamefromtp(maximg+1,false); + for(int curimg=1; curimg<=maximg; curimg++){ + Clipper c; + vector* curdetrpolys; + curdetrpolys=(allpolys[curdetr]); + if(totalpolys[curimg]==NULL && (*curdetrpolys)[curimg]==NULL){ + }else if(totalpolys[curimg]==NULL && (*curdetrpolys)[curimg]!=NULL){ + //if(i>0){ + //mexPrintf("from new\n"); + //mexEvalString("drawnow;"); + //} + tmpunionpolys[curimg]=(*curdetrpolys)[curimg]; + totalarea+=PsArea(*(*curdetrpolys)[curimg]); + }else if(totalpolys[curimg]!=NULL && (*curdetrpolys)[curimg]==NULL){ + //mexPrintf("from prev\n"); + //mexEvalString("drawnow;"); + tmpunionpolys[curimg]=totalpolys[curimg]; + totalarea+=PsArea(*(totalpolys[curimg])); + tuprequiresdelete[curimg]=tprequiresdelete[curimg]; + tupcamefromtp[curimg]=true; + }else{ + //mexPrintf("unioning\n"); + //mexEvalString("drawnow;"); + c.AddPolygons(*(totalpolys[curimg]),ptSubject); + c.AddPolygons(*((*curdetrpolys)[curimg]),ptClip); + tmpunionpolys[curimg]=new Polygons(); + c.Execute(ctUnion,*(tmpunionpolys[curimg]),pftNonZero,pftNonZero); + tuprequiresdelete[curimg]=true; + totalarea+=PsArea(*tmpunionpolys[curimg]); + //mexPrintf("doneunion\n"); + //mexEvalString("drawnow;"); + } + } + prevcontribution[curdetr]=totalarea-totalcoverage; + if(totalarea>bestarea){ + bestarea=totalarea; + for(int curimg=1; curimg<=maximg; curimg++){ + if(buprequiresdelete[curimg] && !bupcamefromtp[curimg]) delete bestunionpolys[curimg]; + } + bestunionpolys=tmpunionpolys; + buprequiresdelete=tuprequiresdelete; + bupcamefromtp=tupcamefromtp; + bestdetr=curdetr; + //mexPrintf("new best: %d, area %f\n", bestdetr,bestarea); + //mexEvalString("drawnow;"); + }else{ + for(int curimg=1; curimg<=maximg; curimg++){ + if(tuprequiresdelete[curimg] && !tupcamefromtp[curimg]) delete tmpunionpolys[curimg]; + } + } + } + outdata[i]=bestdetr; + mexPrintf("selected: %d, area %f; completed %d / %d\n", bestdetr, bestarea, i+1,*ntofind); + mexEvalString("drawnow;"); + outscores[i]=bestarea-totalcoverage; + totalcoverage=bestarea; + for(int curimg=1; curimg<=maximg; curimg++){ + if(tprequiresdelete[curimg] && !bupcamefromtp[curimg]) delete totalpolys[curimg]; + } + totalpolys=bestunionpolys; + tprequiresdelete=buprequiresdelete; + } + for(int curimg=1; curimg<=maximg; curimg++){ + if(tprequiresdelete[curimg]) delete totalpolys[curimg]; + for(int curdetr=1; curdetr<=maxdetr; curdetr++){ + //mexPrintf("allpolydelete:\n"); + //mexEvalString("drawnow;"); + if((*allpolys[curdetr])[curimg]!=NULL) delete (*allpolys[curdetr])[curimg]; + } + // mexPrintf("allpolyfinaldelete:\n"); + // mexEvalString("drawnow;"); + } + for(int curdetr=1;curdetr<=maxdetr;curdetr++){ + delete allpolys[curdetr]; + } + +} + diff --git a/clipper/greedySelectDetrsCoveragemex.mexa64 b/clipper/greedySelectDetrsCoveragemex.mexa64 new file mode 100755 index 0000000..6bcb545 Binary files /dev/null and b/clipper/greedySelectDetrsCoveragemex.mexa64 differ diff --git a/clipper/perl/perl_readme.txt b/clipper/perl/perl_readme.txt new file mode 100755 index 0000000..abcfa41 --- /dev/null +++ b/clipper/perl/perl_readme.txt @@ -0,0 +1,5 @@ + +A Perl module written by Steffen Müller < smueller@cpan.org > +that wraps the Clipper library can be downloaded from: + +https://metacpan.org/module/Math::Clipper \ No newline at end of file diff --git a/clipper/python/clipper.py b/clipper/python/clipper.py new file mode 100755 index 0000000..7b19c29 --- /dev/null +++ b/clipper/python/clipper.py @@ -0,0 +1,2107 @@ +#=============================================================================== +# # +# Author : Angus Johnson # +# Version : 5.1.0 # +# Date : 1 February 2013 # +# Website : http://www.angusj.com # +# Copyright : Angus Johnson 2010-2013 # +# # +# License: # +# Use, modification & distribution is subject to Boost Software License Ver 1. # +# http://www.boost.org/LICENSE_1_0.txt # +# # +# Attributions: # +# The code in this library is an extension of Bala Vatti's clipping algorithm: # +# "A generic solution to polygon clipping" # +# Communications of the ACM, Vol 35, Issue 7 (July 1992) PP 56-63. # +# http://portal.acm.org/citation.cfm?id=129906 # +# # +# Computer graphics and geometric modeling: implementation and algorithms # +# By Max K. Agoston # +# Springer; 1 edition (January 4, 2005) # +# http://books.google.com/books?q=vatti+clipping+agoston # +# # +# See also: # +# "Polygon Offsetting by Computing Winding Numbers" # +# Paper no. DETC2005-85513 PP. 565-575 # +# ASME 2005 International Design Engineering Technical Conferences # +# and Computers and Information in Engineering Conference (IDETC/CIE2005) # +# September 24-28, 2005 , Long Beach, California, USA # +# http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf # +# # +#=============================================================================== + +import math +from collections import namedtuple +from decimal import Decimal, getcontext + +getcontext().prec = 8 +horizontal = Decimal('-Infinity') + +class ClipType: (Intersection, Union, Difference, Xor) = range(4) +class PolyType: (Subject, Clip) = range(2) +class PolyFillType: (EvenOdd, NonZero, Positive, Negative) = range(4) +class JoinType: (Square, Round, Miter) = range(3) +class EdgeSide: (Left, Right) = range(2) +class Protects: (Neither, Left, Right, Both) = range(4) +class Direction: (LeftToRight, RightToLeft) = range(2) + +Point = namedtuple('Point', 'x y') + +class LocalMinima(object): + leftBound = rightBound = nextLm = None + def __init__(self, y, leftBound, rightBound): + self.y = y + self.leftBound = leftBound + self.rightBound = rightBound + +class Scanbeam(object): + __slots__ = ('y','nextSb') + def __init__(self, y, nextSb = None): + self.y = y + self.nextSb = nextSb + def __repr__(self): + s = 'None' + if self.nextSb is not None: s = '' + return "(y:%i, nextSb:%s)" % (self.y, s) + +class IntersectNode(object): + __slots__ = ('e1','e2','pt','nextIn') + def __init__(self, e1, e2, pt): + self.e1 = e1 + self.e2 = e2 + self.pt = pt + self.nextIn = None + +class OutPt(object): + __slots__ = ('idx','pt','prevOp','nextOp') + def __init__(self, idx, pt): + self.idx = idx + self.pt = pt + self.prevOp = None + self.nextOp = None + +class OutRec(object): + __slots__ = ('idx','bottomPt','isHole','FirstLeft', 'pts','PolyNode') + def __init__(self, idx): + self.idx = idx + self.bottomPt = None + self.isHole = False + self.FirstLeft = None + self.pts = None + self.PolyNode = None + +class JoinRec(object): + __slots__ = ('pt1a','pt1b','poly1Idx','pt2a', 'pt2b','poly2Idx') + +class HorzJoin(object): + edge = None + savedIdx = 0 + prevHj = None + nextHj = None + def __init__(self, edge, idx): + self.edge = edge + self.savedIdx = idx + +#=============================================================================== +# Unit global functions ... +#=============================================================================== +def IntsToPoints(ints): + result = [] + for i in range(0, len(ints), 2): + result.append(Point(ints[i], ints[i+1])) + return result + +def Area(polygon): + # see http://www.mathopenref.com/coordpolygonarea2.html + highI = len(polygon) - 1 + A = (polygon[highI].x + polygon[0].x) * (polygon[0].y - polygon[highI].y) + for i in range(highI): + A += (polygon[i].x + polygon[i+1].x) * (polygon[i+1].y - polygon[i].y) + return float(A) / 2 + +def Orientation(polygon): + return Area(polygon) > 0.0 + +#=============================================================================== +# PolyNode & PolyTree classes (+ ancilliary functions) +#=============================================================================== +class PolyNode(object): + """Node of PolyTree""" + + def __init__(self): + self.Contour = [] + self.Childs = [] + self.Parent = None + self.Index = 0 + self.ChildCount = 0 + + def IsHole(self): + result = True + while (self.Parent is not None): + result = not result + self.Parent = self.Parent.Parent + return result + + def GetNext(self): + if (self.ChildCount > 0): + return self.Childs[0] + else: + return self._GetNextSiblingUp() + + def _AddChild(self, node): + self.Childs.append(node) + node.Index = self.ChildCount + node.Parent = self + self.ChildCount += 1 + + def _GetNextSiblingUp(self): + if (self.Parent is None): + return None + elif (self.Index == self.Parent.ChildCount - 1): + return self.Parent._GetNextSiblingUp() + else: + return self.Parent.Childs[self.Index +1] + +class PolyTree(PolyNode): + """Container for PolyNodes""" + + def __init__(self): + PolyNode.__init__(self) + self._AllNodes = [] + + def Clear(self): + self._AllNodes = [] + self.Childs = [] + self.ChildCount = 0 + + def GetFirst(self): + if (self.ChildCount > 0): + return self.Childs[0] + else: + return None + + def Total(self): + return len(self._AllNodes) + +def _AddPolyNodeToPolygons(polynode, polygons): + """Internal function for PolyTreeToPolygons()""" + if (len(polynode.Contour) > 0): + polygons.append(polynode.Contour) + for i in range(polynode.ChildCount): + _AddPolyNodeToPolygons(polynode.Childs[i], polygons) + +def PolyTreeToPolygons(polyTree): + result = [] + _AddPolyNodeToPolygons(polyTree, result) + return result + +#=============================================================================== +# Edge class +#=============================================================================== + +class Edge(object): + + def __init__(self): + self.xBot, self.yBot, self.xCurr, self.yCurr, = 0, 0, 0, 0 + self.xTop, self.yTop, self.tmpX = 0, 0, 0 + self.dx, self.deltaX , self.deltaY = Decimal(0), Decimal(0), Decimal(0) + self.polyType = PolyType.Subject + self.side = EdgeSide.Left + self.windDelta, self.windCnt, self.windCnt2 = 0, 0, 0 + self.outIdx = -1 + self.nextE, self.prevE, self.nextInLML = None, None, None + self.prevInAEL, self.nextInAEL, self.prevInSEL, self.nextInSEL = None, None, None, None + + def __repr__(self): + return "(%i,%i -> %i,%i {dx:%0.2f} %i {%x})" % \ + (self.xBot, self.yBot, self.xTop, self.yTop, self.dx, self.outIdx, id(self)) + +#=============================================================================== +# ClipperBase class (+ data structs & ancilliary functions) +#=============================================================================== + +def _PointsEqual(pt1, pt2): + return (pt1.x == pt2.x) and (pt1.y == pt2.y) + +def _SlopesEqual(pt1, pt2, pt3, pt4 = None): + if pt4 is None: + return (pt1.y-pt2.y)*(pt2.x-pt3.x) == (pt1.x-pt2.x)*(pt2.y-pt3.y) + else: + return (pt1.y-pt2.y)*(pt3.x-pt4.x) == (pt1.x-pt2.x)*(pt3.y-pt4.y) + +def _SlopesEqual2(e1, e2): + return e1.deltaY * e2.deltaX == e1.deltaX * e2.deltaY + +def _SetDx(e): + e.deltaX = Decimal(e.xTop - e.xBot) + e.deltaY = Decimal(e.yTop - e.yBot) + if e.deltaY == 0: e.dx = horizontal + else: e.dx = e.deltaX/e.deltaY + +def _SwapSides(e1, e2): + side = e1.side + e1.side = e2.side + e2.side = side + +def _SwapPolyIndexes(e1, e2): + idx = e1.outIdx + e1.outIdx = e2.outIdx + e2.outIdx = idx + +def _InitEdge(e, eNext, ePrev, pt, polyType): + e.nextE = eNext + e.prevE = ePrev + e.xCurr = pt.x + e.yCurr = pt.y + if e.yCurr >= e.nextE.yCurr: + e.xBot = e.xCurr + e.yBot = e.yCurr + e.xTop = e.nextE.xCurr + e.yTop = e.nextE.yCurr + e.windDelta = 1 + else: + e.xTop = e.xCurr + e.yTop = e.yCurr + e.xBot = e.nextE.xCurr + e.yBot = e.nextE.yCurr + e.windDelta = -1 + _SetDx(e) + e.outIdx = -1 + e.PolyType = polyType + +def _SwapX(e): + e.xCurr = e.xTop + e.xTop = e.xBot + e.xBot = e.xCurr + +class ClipperBase(object): + + def __init__(self): + self._EdgeList = [] # 2D array + self._LocalMinList = None # single-linked list of LocalMinima + self._CurrentLocMin = None + + def _InsertLocalMinima(self, lm): + if self._LocalMinList is None: + self._LocalMinList = lm + elif lm.y >= self._LocalMinList.y: + lm.nextLm = self._LocalMinList + self._LocalMinList = lm + else: + tmp = self._LocalMinList + while tmp.nextLm is not None and lm.y < tmp.nextLm.y: + tmp = tmp.nextLm + lm.nextLm = tmp.nextLm + tmp.nextLm = lm + + def _AddBoundsToLML(self, e): + e.nextInLML = None + e = e.nextE + while True: + if e.dx == horizontal: + if (e.nextE.yTop < e.yTop) and (e.nextE.xBot > e.prevE.xBot): break + if (e.xTop != e.prevE.xBot): _SwapX(e) + e.nextInLML = e.prevE + elif e.yBot == e.prevE.yBot: break + else: e.nextInLML = e.prevE + e = e.nextE + + if e.dx == horizontal: + if (e.xBot != e.prevE.xBot): _SwapX(e) + lm = LocalMinima(e.prevE.yBot, e.prevE, e) + elif (e.dx < e.prevE.dx): + lm = LocalMinima(e.prevE.yBot, e.prevE, e) + else: + lm = LocalMinima(e.prevE.yBot, e, e.prevE) + lm.leftBound.side = EdgeSide.Left + lm.rightBound.side = EdgeSide.Right + self._InsertLocalMinima(lm) + while True: + if e.nextE.yTop == e.yTop and e.nextE.dx != horizontal: break + e.nextInLML = e.nextE + e = e.nextE + if e.dx == horizontal and e.xBot != e.prevE.xTop: _SwapX(e) + return e.nextE + + def _Reset(self): + lm = self._LocalMinList + if lm is not None: self._CurrentLocMin = lm + while lm is not None: + e = lm.leftBound + while e is not None: + e.xCurr = e.xBot + e.yCurr = e.yBot + e.side = EdgeSide.Left + e.outIdx = -1 + e = e.nextInLML + e = lm.rightBound + while e is not None: + e.xCurr = e.xBot + e.yCurr = e.yBot + e.side = EdgeSide.Right + e.outIdx = -1 + e = e.nextInLML + lm = lm.nextLm + + def AddPolygon(self, polygon, polyType): + ln = len(polygon) + if ln < 3: return False + pg = polygon[:] + j = 0 + # remove duplicate points and co-linear points + for i in range(1, len(polygon)): + if _PointsEqual(pg[j], polygon[i]): + continue + elif (j > 0) and _SlopesEqual(pg[j-1], pg[j], polygon[i]): + if _PointsEqual(pg[j-1], polygon[i]): j -= 1 + else: j += 1 + pg[j] = polygon[i] + if (j < 2): return False + # remove duplicate points and co-linear edges at the loop around + # of the start and end coordinates ... + ln = j +1 + while (ln > 2): + if _PointsEqual(pg[j], pg[0]): j -= 1 + elif _PointsEqual(pg[0], pg[1]) or _SlopesEqual(pg[j], pg[0], pg[1]): + pg[0] = pg[j] + j -= 1 + elif _SlopesEqual(pg[j-1], pg[j], pg[0]): j -= 1 + elif _SlopesEqual(pg[0], pg[1], pg[2]): + for i in range(2, j +1): pg[i-1] = pg[i] + j -= 1 + else: break + ln -= 1 + if ln < 3: return False + edges = [] + for i in range(ln): + edges.append(Edge()) + edges[0].xCurr = pg[0].x + edges[0].yCurr = pg[0].y + _InitEdge(edges[ln-1], edges[0], edges[ln-2], pg[ln-1], polyType) + for i in range(ln-2, 0, -1): + _InitEdge(edges[i], edges[i+1], edges[i-1], pg[i], polyType) + _InitEdge(edges[0], edges[1], edges[ln-1], pg[0], polyType) + e = edges[0] + eHighest = e + while True: + e.xCurr = e.xBot + e.yCurr = e.yBot + if e.yTop < eHighest.yTop: eHighest = e + e = e.nextE + if e == edges[0]: break + # make sure eHighest is positioned so the following loop works safely ... + if eHighest.windDelta > 0: eHighest = eHighest.nextE + if eHighest.dx == horizontal: eHighest = eHighest.nextE + # finally insert each local minima ... + e = eHighest + while True: + e = self._AddBoundsToLML(e) + if e == eHighest: break + self._EdgeList.append(edges) + + def AddPolygons(self, polygons, polyType): + result = False + for p in polygons: + if self.AddPolygon(p, polyType): result = True + return result + + def Clear(self): + self._EdgeList = [] + self._LocalMinList = None + self._CurrentLocMin = None + + def _PopLocalMinima(self): + if self._CurrentLocMin is not None: + self._CurrentLocMin = self._CurrentLocMin.nextLm + +#=============================================================================== +# Clipper class (+ data structs & ancilliary functions) +#=============================================================================== +def _IntersectPoint(edge1, edge2): + if _SlopesEqual2(edge1, edge2): return Point(0,0), False + if edge1.dx == 0: + x = edge1.xBot + if edge2.dx == horizontal: + y = edge2.yBot + else: + b2 = edge2.yBot - Decimal(edge2.xBot)/edge2.dx + y = round(Decimal(x)/edge2.dx + b2) + elif edge2.dx == 0: + x = edge2.xBot + if edge1.dx == horizontal: + y = edge1.yBot + else: + b1 = edge1.yBot - Decimal(edge1.xBot)/edge1.dx + y = round(Decimal(x)/edge1.dx + b1) + else: + b1 = edge1.xBot - edge1.yBot * edge1.dx + b2 = edge2.xBot - edge2.yBot * edge2.dx + m = Decimal(b2-b1)/(edge1.dx - edge2.dx) + y = round(m) + if math.fabs(edge1.dx) < math.fabs(edge2.dx): + x = round(edge1.dx * m + b1) + else: + x = round(edge2.dx * m + b2) + if (y < edge1.yTop) or (y < edge2.yTop): + if (edge1.yTop > edge2.yTop): + return Point(edge1.xTop,edge1.yTop), _TopX(edge2, edge1.yTop) < edge1.xTop + else: + return Point(edge2.xTop,edge2.yTop), _TopX(edge1, edge2.yTop) > edge2.xTop + else: + return Point(x,y), True + +def _TopX(e, currentY): + if currentY == e.yTop: return e.xTop + elif e.xTop == e.xBot: return e.xBot + else: return e.xBot + round(e.dx * Decimal(currentY - e.yBot)) + +def _E2InsertsBeforeE1(e1,e2): + if e2.xCurr == e1.xCurr: + return e2.dx > e1.dx + else: + return e2.xCurr < e1.xCurr + +def _IsMinima(e): + return e is not None and e.prevE.nextInLML != e and e.nextE.nextInLML != e + +def _IsMaxima(e, y): + return e is not None and e.yTop == y and e.nextInLML is None + +def _IsIntermediate(e, y): + return e.yTop == y and e.nextInLML is not None + +def _GetMaximaPair(e): + if not _IsMaxima(e.nextE, e.yTop) or e.nextE.xTop != e.xTop: + return e.prevE + else: + return e.nextE + +def _GetnextInAEL(e, direction): + if direction == Direction.LeftToRight: return e.nextInAEL + else: return e.prevInAEL + +def _ProtectLeft(val): + if val: return Protects.Both + else: return Protects.Right + +def _ProtectRight(val): + if val: return Protects.Both + else: return Protects.Left + +def _SwapIntersectNodes(int1, int2): + e1 = int1.e1 + e2 = int1.e2 + p = int1.pt + int1.e1 = int2.e1 + int1.e2 = int2.e2 + int1.pt = int2.pt + int2.e1 = e1 + int2.e2 = e2 + int2.pt = p + +def _GetDx(pt1, pt2): + if (pt1.y == pt2.y): return horizontal + else: return Decimal(pt2.x - pt1.x)/(pt2.y - pt1.y) + +def _ProcessParam1BeforeParam2(node1, node2): + if node1.pt.y != node2.pt.y: + return node1.pt.y > node2.pt.y + if node1.e1 == node2.e1 or node1.e2 == node2.e1: + result = node2.pt.x > node1.pt.x + if node2.e1.dx > 0: result = not result + return result + elif node1.e1 == node2.e2 or node1.e2 == node2.e2: + result = node2.pt.x > node1.pt.x + if node2.e2.dx > 0: result = not result + return result + else: + return node2.pt.x > node1.pt.x + +def _Param1RightOfParam2(outRec1, outRec2): + while outRec1 is not None: + outRec1 = outRec1.FirstLeft + if outRec1 == outRec2: return True + return False + +def _FirstParamIsbottomPt(btmPt1, btmPt2): + p = btmPt1.prevOp + while _PointsEqual(p.pt, btmPt1.pt) and (p != btmPt1): p = p.prevOp + dx1p = abs(_GetDx(btmPt1.pt, p.pt)) + p = btmPt1.nextOp + while _PointsEqual(p.pt, btmPt1.pt) and (p != btmPt1): p = p.nextOp + dx1n = abs(_GetDx(btmPt1.pt, p.pt)) + + p = btmPt2.prevOp + while _PointsEqual(p.pt, btmPt2.pt) and (p != btmPt2): p = p.prevOp + dx2p = abs(_GetDx(btmPt2.pt, p.pt)) + p = btmPt2.nextOp + while _PointsEqual(p.pt, btmPt2.pt) and (p != btmPt2): p = p.nextOp + dx2n = abs(_GetDx(btmPt2.pt, p.pt)) + return (dx1p >= dx2p and dx1p >= dx2n) or (dx1n >= dx2p and dx1n >= dx2n) + +def _GetBottomPt(pp): + dups = None + p = pp.nextOp + while p != pp: + if p.pt.y > pp.pt.y: + pp = p + dups = None + elif p.pt.y == pp.pt.y and p.pt.x <= pp.pt.x: + if p.pt.x < pp.pt.x: + dups = None + pp = p + else: + if p.nextOp != pp and p.prevOp != pp: dups = p + p = p.nextOp + if dups is not None: + while dups != p: + if not _FirstParamIsbottomPt(p, dups): pp = dups + dups = dups.nextOp + while not _PointsEqual(dups.pt, pp.pt): dups = dups.nextOp + return pp + +def _GetLowermostRec(outRec1, outRec2): + outPt1 = outRec1.bottomPt + outPt2 = outRec2.bottomPt + if (outPt1.pt.y > outPt2.pt.y): return outRec1 + elif (outPt1.pt.y < outPt2.pt.y): return outRec2 + elif (outPt1.pt.x < outPt2.pt.x): return outRec1 + elif (outPt1.pt.x > outPt2.pt.x): return outRec2 + elif (outPt1.nextOp == outPt1): return outRec2 + elif (outPt2.nextOp == outPt2): return outRec1 + elif _FirstParamIsbottomPt(outPt1, outPt2): return outRec1 + else: return outRec2 + +def _SetHoleState(e, outRec, polyOutList): + isHole = False + e2 = e.prevInAEL + while e2 is not None: + if e2.outIdx >= 0: + isHole = not isHole + if outRec.FirstLeft is None: + outRec.FirstLeft = polyOutList[e2.outIdx] + e2 = e2.prevInAEL + outRec.isHole = isHole + +def _AddOutPt(e, pt, polyOutList): + toFront = e.side == EdgeSide.Left + if e.outIdx < 0: + outRec = OutRec(len(polyOutList)) + e.outIdx = outRec.idx + polyOutList.append(outRec) + op = OutPt(outRec.idx, pt) + op.nextOp = op + op.prevOp = op + outRec.pts = op + outRec.bottomPt = op + _SetHoleState(e, outRec, polyOutList) + else: + outRec = polyOutList[e.outIdx] + op = outRec.pts + if (toFront and _PointsEqual(pt, op.pt)) or \ + (not toFront and _PointsEqual(pt, op.prevOp.pt)): return + op2 = OutPt(outRec.idx, pt) + if (op2.pt.y == outRec.bottomPt.pt.y) and \ + (op2.pt.x < outRec.bottomPt.pt.x): + outRec.bottomPt = op2 + op2.nextOp = op + op2.prevOp = op.prevOp + op.prevOp.nextOp = op2 + op.prevOp = op2 + if toFront: outRec.pts = op2 + +def _PointCount(pts): + if pts is None: return 0 + p = pts + result = 0 + while True: + result += 1 + p = p.nextOp + if p == pts: break + return result + +def _PointIsVertex(pt, outPts): + op = outPts + while True: + if _PointsEqual(op.pt, pt): return True + op = op.nextOp + if op == outPts: break + return False + +def _ReversePolyPtLinks(pp): + if pp is None: return + pp1 = pp + while True: + pp2 = pp1.nextOp + pp1.nextOp = pp1.prevOp + pp1.prevOp = pp2; + pp1 = pp2 + if pp1 == pp: break + +def _DoEdge1(e1, e2, pt, polyOutList): + _AddOutPt(e1, pt, polyOutList) + _SwapSides(e1, e2) + _SwapPolyIndexes(e1, e2) + +def _DoEdge2(e1, e2, pt, polyOutList): + _AddOutPt(e2, pt, polyOutList) + _SwapSides(e1, e2) + _SwapPolyIndexes(e1, e2) + +def _DoBothEdges(e1, e2, pt, polyOutList): + _AddOutPt(e1, pt, polyOutList) + _AddOutPt(e2, pt, polyOutList) + _SwapSides(e1, e2) + _SwapPolyIndexes(e1, e2) + +def _FixupOutPolygon(outRec): + lastOK = None + outRec.pts = outRec.bottomPt + pp = outRec.pts + while True: + if pp.prevOp == pp or pp.nextOp == pp.prevOp: + outRec.pts = None + outRec.bottomPt = None + return + if _PointsEqual(pp.pt, pp.nextOp.pt) or \ + _SlopesEqual(pp.prevOp.pt, pp.pt, pp.nextOp.pt): + lastOK = None + if pp == outRec.bottomPt: + outRec.bottomPt = None + pp.prevOp.nextOp = pp.nextOp + pp.nextOp.prevOp = pp.prevOp + pp = pp.prevOp + elif pp == lastOK: break + else: + if lastOK is None: lastOK = pp + pp = pp.nextOp + if outRec.bottomPt is None: + outRec.bottomPt = _GetBottomPt(pp) + outRec.bottomPt.idx = outRec.idx + outRec.pts = outRec.bottomPt + +def _FixHoleLinkage(outRec): + if outRec.FirstLeft is None or \ + (outRec.isHole != outRec.FirstLeft.isHole and \ + outRec.FirstLeft.pts is not None): return + orfl = outRec.FirstLeft + while orfl is not None and \ + (orfl.isHole == outRec.isHole or orfl.pts is None): + orfl = orfl.FirstLeft + outRec.FirstLeft = orfl + +def _GetOverlapSegment(pt1a, pt1b, pt2a, pt2b): + # precondition: segments are co-linear + if abs(pt1a.x - pt1b.x) > abs(pt1a.y - pt1b.y): + if pt1a.x > pt1b.x: tmp = pt1a; pt1a = pt1b; pt1b = tmp + if pt2a.x > pt2b.x: tmp = pt2a; pt2a = pt2b; pt2b = tmp + if (pt1a.x > pt2a.x): pt1 = pt1a + else: pt1 = pt2a + if (pt1b.x < pt2b.x): pt2 = pt1b + else: pt2 = pt2b + return pt1, pt2, pt1.x < pt2.x + else: + if pt1a.y < pt1b.y: tmp = pt1a; pt1a = pt1b; pt1b = tmp + if pt2a.y < pt2b.y: tmp = pt2a; pt2a = pt2b; pt2b = tmp + if (pt1a.y < pt2a.y): pt1 = pt1a + else: pt1 = pt2a + if (pt1b.y > pt2b.y): pt2 = pt1b + else: pt2 = pt2b + return pt1, pt2, pt1.y > pt2.y + + +def _FindSegment(outPt, pt1, pt2): + if outPt is None: return outPt, pt1, pt2, False + pt1a = pt1; pt2a = pt2 + outPt2 = outPt + while True: + if _SlopesEqual(pt1a, pt2a, outPt.pt, outPt.prevOp.pt) and _SlopesEqual(pt1a, pt2a, outPt.pt): + pt1, pt2, overlap = _GetOverlapSegment(pt1a, pt2a, outPt.pt, outPt.prevOp.pt) + if overlap: return outPt, pt1, pt2, True + outPt = outPt.nextOp + if outPt == outPt2: return outPt, pt1, pt2, False + +def _Pt3IsBetweenPt1AndPt2(pt1, pt2, pt3): + if _PointsEqual(pt1, pt3) or _PointsEqual(pt2, pt3): return True + elif pt1.x != pt2.x: return (pt1.x < pt3.x) == (pt3.x < pt2.x) + else: return (pt1.y < pt3.y) == (pt3.y < pt2.y) + +def _InsertPolyPtBetween(outPt1, outPt2, pt): + if outPt1 == outPt2: raise Exception("JoinError") + result = OutPt(outPt1.idx, pt) + if outPt2 == outPt1.nextOp: + outPt1.nextOp = result + outPt2.prevOp = result + result.nextOp = outPt2 + result.prevOp = outPt1 + else: + outPt2.nextOp = result + outPt1.prevOp = result + result.nextOp = outPt1 + result.prevOp = outPt2 + return result + +def _PointInPolygon(pt, outPt): + result = False + outPt2 = outPt + while True: + if ((((outPt2.pt.y <= pt.y) and (pt.y < outPt2.prevOp.pt.y)) or \ + ((outPt2.prevOp.pt.y <= pt.y) and (pt.y < outPt2.pt.y))) and \ + (pt.x < (outPt2.prevOp.pt.x - outPt2.pt.x) * (pt.y - outPt2.pt.y) / \ + (outPt2.prevOp.pt.y - outPt2.pt.y) + outPt2.pt.x)): result = not result + outPt2 = outPt2.nextOp + if outPt2 == outPt: break + +def _Poly2ContainsPoly1(outPt1, outPt2): + outPt = outPt1 + while True: + if not _PointIsVertex(outPt.pt, outPt2): break + outPt = outPt.nextOp + if outPt == outPt1: break + while True: + result = _PointInPolygon(outPt.pt, outPt2) + outPt = outPt.nextOp + if not result or outPt == outPt1: break + return result + +class Clipper(ClipperBase): + + def __init__(self): + ClipperBase.__init__(self) + self._PolyOutList = [] + self._ClipType = ClipType.Intersection + self._Scanbeam = None + self._ActiveEdges = None + self._SortedEdges = None + self._IntersectNodes = None + self._ClipFillType = PolyFillType.EvenOdd + self._SubjFillType = PolyFillType.EvenOdd + self._ExecuteLocked = False + self._ReverseOutput = False + self._UsingPolyTree = False + self._JoinList = None + self._HorzJoins = None + + def _Reset(self): + ClipperBase._Reset(self) + self._Scanbeam = None + self._PolyOutList = [] + lm = self._LocalMinList + while lm is not None: + self._InsertScanbeam(lm.y) + self._InsertScanbeam(lm.leftBound.yTop) + lm = lm.nextLm + + def Clear(self): + self._PolyOutList = [] + ClipperBase.Clear(self) + + def _InsertScanbeam(self, y): + if self._Scanbeam is None: + self._Scanbeam = Scanbeam(y) + elif y > self._Scanbeam.y: + self._Scanbeam = Scanbeam(y, self._Scanbeam) + else: + sb = self._Scanbeam + while sb.nextSb is not None and y <= sb.nextSb.y: + sb = sb.nextSb + if y == sb.y: return + newSb = Scanbeam(y, sb.nextSb) + sb.nextSb = newSb + + def _PopScanbeam(self): + result = self._Scanbeam.y + self._Scanbeam = self._Scanbeam.nextSb + return result + + def _SetWindingCount(self, edge): + e = edge.prevInAEL + while e is not None and e.PolyType != edge.PolyType: + e = e.prevInAEL + if e is None: + edge.windCnt = edge.windDelta + edge.windCnt2 = 0 + e = self._ActiveEdges + elif self._IsEvenOddFillType(edge): + edge.windCnt = 1 + edge.windCnt2 = e.windCnt2 + e = e.nextInAEL + else: + if e.windCnt * e.windDelta < 0: + if (abs(e.windCnt) > 1): + if (e.windDelta * edge.windDelta < 0): edge.windCnt = e.windCnt + else: edge.windCnt = e.windCnt + edge.windDelta + else: + edge.windCnt = e.windCnt + e.windDelta + edge.windDelta + elif (abs(e.windCnt) > 1) and (e.windDelta * edge.windDelta < 0): + edge.windCnt = e.windCnt + elif e.windCnt + edge.windDelta == 0: + edge.windCnt = e.windCnt + else: + edge.windCnt = e.windCnt + edge.windDelta + edge.windCnt2 = e.windCnt2 + e = e.nextInAEL + # update windCnt2 ... + if self._IsEvenOddAltFillType(edge): + while (e != edge): + if edge.windCnt2 == 0: edge.windCnt2 = 1 + else: edge.windCnt2 = 0 + e = e.nextInAEL + else: + while (e != edge): + edge.windCnt2 += e.windDelta + e = e.nextInAEL + + def _IsEvenOddFillType(self, edge): + if edge.PolyType == PolyType.Subject: + return self._SubjFillType == PolyFillType.EvenOdd + else: + return self._ClipFillType == PolyFillType.EvenOdd + + def _IsEvenOddAltFillType(self, edge): + if edge.PolyType == PolyType.Subject: + return self._ClipFillType == PolyFillType.EvenOdd + else: + return self._SubjFillType == PolyFillType.EvenOdd + + def _IsContributing(self, edge): + result = True + if edge.PolyType == PolyType.Subject: + pft = self._SubjFillType + pft2 = self._ClipFillType + else: + pft = self._ClipFillType + pft2 = self._SubjFillType + if pft == PolyFillType.EvenOdd or pft == PolyFillType.NonZero: + result = abs(edge.windCnt) == 1 + elif pft == PolyFillType.Positive: + result = edge.windCnt == 1 + else: result = edge.windCnt == -1 + if not result: return False + + if self._ClipType == ClipType.Intersection: ########### + if pft2 == PolyFillType.EvenOdd or pft2 == PolyFillType.NonZero: + return edge.windCnt2 != 0 + elif pft2 == PolyFillType.Positive: + return edge.windCnt2 > 0 + else: + return edge.windCnt2 < 0 # Negative + elif self._ClipType == ClipType.Union: ########### + if pft2 == PolyFillType.EvenOdd or pft2 == PolyFillType.NonZero: + return edge.windCnt2 == 0 + elif pft2 == PolyFillType.Positive: + return edge.windCnt2 <= 0 + else: return edge.windCnt2 >= 0 # Negative + elif self._ClipType == ClipType.Difference: ########### + if edge.PolyType == PolyType.Subject: + if pft2 == PolyFillType.EvenOdd or pft2 == PolyFillType.NonZero: + return edge.windCnt2 == 0 + elif edge.PolyType == PolyFillType.Positive: + return edge.windCnt2 <= 0 + else: + return edge.windCnt2 >= 0 + else: + if pft2 == PolyFillType.EvenOdd or pft2 == PolyFillType.NonZero: + return edge.windCnt2 != 0 + elif pft2 == PolyFillType.Positive: + return edge.windCnt2 > 0 + else: + return edge.windCnt2 < 0 + else: # self._ClipType == ClipType.XOR: ########### + if pft2 == PolyFillType.EvenOdd or pft2 == PolyFillType.NonZero: + return edge.windCnt2 != 0 + elif pft2 == PolyFillType.Positive: + return edge.windCnt2 > 0 + else: + return edge.windCnt2 < 0 + + def _AddEdgeToSEL(self, edge): + if self._SortedEdges is None: + self._SortedEdges = edge + edge.prevInSEL = None + edge.nextInSEL = None + else: + # add edge to front of list ... + edge.nextInSEL = self._SortedEdges + edge.prevInSEL = None + self._SortedEdges.prevInSEL = edge + self._SortedEdges = edge + + def _CopyAELToSEL(self): + e = self._ActiveEdges + self._SortedEdges = e + while e is not None: + e.prevInSEL = e.prevInAEL + e.nextInSEL = e.nextInAEL + e = e.nextInAEL + + def _InsertEdgeIntoAEL(self, edge): + edge.prevInAEL = None + edge.nextInAEL = None + if self._ActiveEdges is None: + self._ActiveEdges = edge + elif _E2InsertsBeforeE1(self._ActiveEdges, edge): + edge.nextInAEL = self._ActiveEdges + self._ActiveEdges.prevInAEL = edge + self._ActiveEdges = edge + else: + e = self._ActiveEdges + while e.nextInAEL is not None and \ + not _E2InsertsBeforeE1(e.nextInAEL, edge): + e = e.nextInAEL + edge.nextInAEL = e.nextInAEL + if e.nextInAEL is not None: e.nextInAEL.prevInAEL = edge + edge.prevInAEL = e + e.nextInAEL = edge + + def _InsertLocalMinimaIntoAEL(self, botY): + while self._CurrentLocMin is not None and \ + self._CurrentLocMin.y == botY: + lb = self._CurrentLocMin.leftBound + rb = self._CurrentLocMin.rightBound + self._InsertEdgeIntoAEL(lb) + self._InsertScanbeam(lb.yTop) + self._InsertEdgeIntoAEL(rb) + if self._IsEvenOddFillType(lb): + lb.windDelta = 1 + rb.windDelta = 1 + else: + rb.windDelta = -lb.windDelta + self._SetWindingCount(lb) + rb.windCnt = lb.windCnt + rb.windCnt2 = lb.windCnt2 + if rb.dx == horizontal: + self._AddEdgeToSEL(rb) + self._InsertScanbeam(rb.nextInLML.yTop) + else: + self._InsertScanbeam(rb.yTop) + if self._IsContributing(lb): + self._AddLocalMinPoly(lb, rb, Point(lb.xCurr, self._CurrentLocMin.y)) + + if rb.outIdx >= 0 and rb.dx == horizontal and self._HorzJoins is not None: + hj = self._HorzJoins + while True: + dummy1, dummy2, overlap = _GetOverlapSegment(Point(hj.edge.xBot, hj.edge.yBot), + Point(hj.edge.xTop, hj.edge.yTop), + Point(rb.xBot, rb.yBot), + Point(rb.xTop, rb.yTop)) + if overlap: + self._AddJoin(hj.edge, rb, hj.savedIdx) + hj = hj.nextHj + if hj == self._HorzJoins: break + + if (lb.nextInAEL != rb): + + if rb.outIdx >= 0 and rb.prevInAEL.outIdx >= 0 and _SlopesEqual2(rb.prevInAEL, rb): + self._AddJoin(rb, rb.prevInAEL) + + e = lb.nextInAEL + pt = Point(lb.xCurr, lb.yCurr) + while e != rb: + self._IntersectEdges(rb, e, pt) + e = e.nextInAEL + self._PopLocalMinima() + + def _SwapPositionsInAEL(self, e1, e2): + if e1.nextInAEL == e2: + nextE = e2.nextInAEL + if nextE is not None: nextE.prevInAEL = e1 + prevE = e1.prevInAEL + if prevE is not None: prevE.nextInAEL = e2 + e2.prevInAEL = prevE + e2.nextInAEL = e1 + e1.prevInAEL = e2 + e1.nextInAEL = nextE + elif e2.nextInAEL == e1: + nextE = e1.nextInAEL + if nextE is not None: nextE.prevInAEL = e2 + prevE = e2.prevInAEL + if prevE is not None: prevE.nextInAEL = e1 + e1.prevInAEL = prevE + e1.nextInAEL = e2 + e2.prevInAEL = e1 + e2.nextInAEL = nextE + else: + nextE = e1.nextInAEL + prevE = e1.prevInAEL + e1.nextInAEL = e2.nextInAEL + if e1.nextInAEL is not None: e1.nextInAEL.prevInAEL = e1 + e1.prevInAEL = e2.prevInAEL + if e1.prevInAEL is not None: e1.prevInAEL.nextInAEL = e1 + e2.nextInAEL = nextE + if e2.nextInAEL is not None: e2.nextInAEL.prevInAEL = e2 + e2.prevInAEL = prevE + if e2.prevInAEL is not None: e2.prevInAEL.nextInAEL = e2 + if e1.prevInAEL is None: self._ActiveEdges = e1 + elif e2.prevInAEL is None: self._ActiveEdges = e2 + + def _SwapPositionsInSEL(self, e1, e2): + if e1.nextInSEL == e2: + nextE = e2.nextInSEL + if nextE is not None: nextE.prevInSEL = e1 + prevE = e1.prevInSEL + if prevE is not None: prevE.nextInSEL = e2 + e2.prevInSEL = prevE + e2.nextInSEL = e1 + e1.prevInSEL = e2 + e1.nextInSEL = nextE + elif e2.nextInSEL == e1: + nextE = e1.nextInSEL + if nextE is not None: nextE.prevInSEL = e2 + prevE = e2.prevInSEL + if prevE is not None: prevE.nextInSEL = e1 + e1.prevInSEL = prevE + e1.nextInSEL = e2 + e2.prevInSEL = e1 + e2.nextInSEL = nextE + else: + nextE = e1.nextInSEL + prevE = e1.prevInSEL + e1.nextInSEL = e2.nextInSEL + e1.nextInSEL = e2.nextInSEL + if e1.nextInSEL is not None: e1.nextInSEL.prevInSEL = e1 + e1.prevInSEL = e2.prevInSEL + if e1.prevInSEL is not None: e1.prevInSEL.nextInSEL = e1 + e2.nextInSEL = nextE + if e2.nextInSEL is not None: e2.nextInSEL.prevInSEL = e2 + e2.prevInSEL = prevE + if e2.prevInSEL is not None: e2.prevInSEL.nextInSEL = e2 + if e1.prevInSEL is None: self._SortedEdges = e1 + elif e2.prevInSEL is None: self._SortedEdges = e2 + + def _IsTopHorz(self, xPos): + e = self._SortedEdges + while e is not None: + if (xPos >= min(e.xCurr,e.xTop)) and (xPos <= max(e.xCurr,e.xTop)): + return False + e = e.nextInSEL + return True + + def _ProcessHorizontal(self, horzEdge): + if horzEdge.xCurr < horzEdge.xTop: + horzLeft = horzEdge.xCurr + horzRight = horzEdge.xTop + direction = Direction.LeftToRight + else: + horzLeft = horzEdge.xTop + horzRight = horzEdge.xCurr + direction = Direction.RightToLeft + eMaxPair = None + if horzEdge.nextInLML is None: + eMaxPair = _GetMaximaPair(horzEdge) + e = _GetnextInAEL(horzEdge, direction) + while e is not None: + eNext = _GetnextInAEL(e, direction) + if eMaxPair is not None or \ + ((direction == Direction.LeftToRight) and (e.xCurr <= horzRight)) or \ + ((direction == Direction.RightToLeft) and (e.xCurr >= horzLeft)): + if (e.xCurr == horzEdge.xTop) and eMaxPair is None: + if _SlopesEqual2(e, horzEdge.nextInLML): + if horzEdge.outIdx >= 0 and e.outIdx >= 0: + self._AddJoin(horzEdge.nextInLML, e, horzEdge.outIdx) + break + elif e.dx < horzEdge.nextInLML.dx: break + if e == eMaxPair: + if direction == Direction.LeftToRight: + self._IntersectEdges(horzEdge, e, Point(e.xCurr, horzEdge.yCurr)) + else: + self._IntersectEdges(e, horzEdge, Point(e.xCurr, horzEdge.yCurr)) + return + elif e.dx == horizontal and not _IsMinima(e) and e.xCurr <= e.xTop: + if direction == Direction.LeftToRight: + self._IntersectEdges(horzEdge, e, Point(e.xCurr, horzEdge.yCurr), + _ProtectRight(not self._IsTopHorz(e.xCurr))) + else: + self._IntersectEdges(e, horzEdge, Point(e.xCurr, horzEdge.yCurr), + _ProtectLeft(not self._IsTopHorz(e.xCurr))) + elif (direction == Direction.LeftToRight): + self._IntersectEdges(horzEdge, e, Point(e.xCurr, horzEdge.yCurr), + _ProtectRight(not self._IsTopHorz(e.xCurr))) + else: + self._IntersectEdges(e, horzEdge, Point(e.xCurr, horzEdge.yCurr), + _ProtectLeft(not self._IsTopHorz(e.xCurr))) + self._SwapPositionsInAEL(horzEdge, e) + elif (self._SortedEdges is not None) and \ + ((direction == Direction.LeftToRight and e.xCurr > horzRight) or \ + (direction == Direction.RightToLeft and e.xCurr < horzLeft)): break + e = eNext + if horzEdge.nextInLML is not None: + if horzEdge.outIdx >= 0: + _AddOutPt(horzEdge, Point(horzEdge.xTop, horzEdge.yTop), self._PolyOutList) + self._UpdateEdgeIntoAEL(horzEdge) + else: + if horzEdge.outIdx >= 0: + self._IntersectEdges(horzEdge, eMaxPair, \ + Point(horzEdge.xTop, horzEdge.yCurr), Protects.Both) + if eMaxPair.outIdx >= 0: raise Exception("Clipper: Horizontal Error") + self._DeleteFromAEL(eMaxPair) + self._DeleteFromAEL(horzEdge) + + def _ProcessHorizontals(self): + while self._SortedEdges is not None: + e = self._SortedEdges + self._DeleteFromSEL(e) + self._ProcessHorizontal(e) + + def _AddJoin(self, e1, e2, e1OutIdx = -1, e2OutIdx = -1): + jr = JoinRec() + if e1OutIdx >= 0: jr.poly1Idx = e1OutIdx + else: jr.poly1Idx = e1.outIdx + jr.pt1a = Point(e1.xCurr, e1.yCurr) + jr.pt1b = Point(e1.xTop, e1.yTop) + if e2OutIdx >= 0: jr.poly2Idx = e2OutIdx + else: jr.poly2Idx = e2.outIdx + jr.pt2a = Point(e2.xCurr, e2.yCurr) + jr.pt2b = Point(e2.xTop, e2.yTop) + if self._JoinList is None: + self._JoinList = [] + self._JoinList.append(jr) + + def _FixupJoinRecs(self, jr, outPt, startIdx): + for i in range(startIdx, len(self._JoinList)): + jr2 = self._JoinList[i] + if jr2.poly1Idx == jr.poly1Idx and _PointIsVertex(jr2.pt1a, outPt): + jr2.poly1Idx = jr.poly2Idx + if jr2.poly2Idx == jr.poly1Idx and _PointIsVertex(jr2.pt2a, outPt): + jr2.poly2Idx = jr.poly2Idx + + def _AddHorzJoin(self, e, idx): + hj = HorzJoin(e, idx) + if self._HorzJoins == None: + self._HorzJoins = hj + hj.nextHj = hj + hj.prevHj = hj + else: + hj.nextHj = self._HorzJoins + hj.prevHj = self._HorzJoins.prevHj + self._HorzJoins.prevHj.nextHj = hj + self._HorzJoins.prevHj = hj + + def _AddIntersectNode(self, e1, e2, pt): + newNode = IntersectNode(e1, e2, pt) + if self._IntersectNodes is None: + self._IntersectNodes = newNode + elif _ProcessParam1BeforeParam2(newNode, self._IntersectNodes): + newNode.nextIn = self._IntersectNodes + self._IntersectNodes = newNode + else: + node = self._IntersectNodes + while node.nextIn is not None and \ + _ProcessParam1BeforeParam2(node.nextIn, newNode): + node = node.nextIn + newNode.nextIn = node.nextIn + node.nextIn = newNode + + def _ProcessIntersections(self, botY, topY): + self._BuildIntersectList(botY, topY) + if self._IntersectNodes is None: return True + elif not self._FixupIntersections(): return False + self._ProcessIntersectList() + self._IntersectNodes = None + return True + + def _BuildIntersectList(self, botY, topY): + if self._ActiveEdges is None: return + + e = self._ActiveEdges + self._SortedEdges = e + while e is not None: + e.prevInSEL = e.prevInAEL + e.nextInSEL = e.nextInAEL + e.tmpX = _TopX(e, topY) + e = e.nextInAEL + try: + isModified = True + while isModified and self._SortedEdges is not None: + isModified = False + e = self._SortedEdges + while e.nextInSEL is not None: + eNext = e.nextInSEL + if e.tmpX <= eNext.tmpX: + e = eNext + continue + pt, intersected = _IntersectPoint(e, eNext) + if not intersected: + e = eNext + continue + if pt.y > botY: + pt = Point(_TopX(e, botY), botY) + self._AddIntersectNode(e, eNext, pt) + self._SwapPositionsInSEL(e, eNext) + isModified = True + if e.prevInSEL is not None: + e.prevInSEL.nextInSEL = None + else: + break + finally: + self._SortedEdges = None + + def _ProcessIntersectList(self): + while self._IntersectNodes is not None: + node = self._IntersectNodes + self._IntersectEdges(node.e1, node.e2, node.pt, Protects.Both) + self._SwapPositionsInAEL(node.e1, node.e2) + self._IntersectNodes = node.nextIn + + def _DeleteFromAEL(self, e): + aelPrev = e.prevInAEL + aelNext = e.nextInAEL + if aelPrev is None and aelNext is None and e != self._ActiveEdges: + return + if aelPrev is not None: + aelPrev.nextInAEL = aelNext + else: + self._ActiveEdges = aelNext + if aelNext is not None: + aelNext.prevInAEL = aelPrev + e.nextInAEL = None + e.prevInAEL = None + + def _DeleteFromSEL(self, e): + SELPrev = e.prevInSEL + SELNext = e.nextInSEL + if SELPrev is None and SELNext is None and e != self._SortedEdges: + return + if SELPrev is not None: + SELPrev.nextInSEL = SELNext + else: + self._SortedEdges = SELNext + if SELNext is not None: + SELNext.prevInSEL = SELPrev + e.nextInSEL = None + e.prevInSEL = None + + def _IntersectEdges(self, e1, e2, pt, protects = Protects.Neither): + e1stops = protects & Protects.Left == 0 and \ + e1.nextInLML is None and \ + e1.xTop == pt.x and e1.yTop == pt.y + e2stops = protects & Protects.Right == 0 and \ + e2.nextInLML is None and \ + e2.xTop == pt.x and e2.yTop == pt.y + e1Contributing = e1.outIdx >= 0 + e2contributing = e2.outIdx >= 0 + + if e1.PolyType == e2.PolyType: + if self._IsEvenOddFillType(e1): + e1Wc = e1.windCnt + e1.windCnt = e2.windCnt + e2.windCnt = e1Wc + else: + if e1.windCnt + e2.windDelta == 0: e1.windCnt = -e1.windCnt + else: e1.windCnt += e2.windDelta + if e2.windCnt - e1.windDelta == 0: e2.windCnt = -e2.windCnt + else: e2.windCnt -= e1.windDelta + else: + if not self._IsEvenOddFillType(e2): e1.windCnt2 += e2.windDelta + elif e1.windCnt2 == 0: e1.windCnt2 = 1 + else: e1.windCnt2 = 0 + if not self._IsEvenOddFillType(e1): e2.windCnt2 -= e1.windDelta + elif e2.windCnt2 == 0: e2.windCnt2 = 1 + else: e2.windCnt2 = 0 + + if e1.PolyType == PolyType.Subject: + e1FillType = self._SubjFillType + e1FillType2 = self._ClipFillType + else: + e1FillType = self._ClipFillType + e1FillType2 = self._SubjFillType + + if e2.PolyType == PolyType.Subject: + e2FillType = self._SubjFillType + e2FillType2 = self._ClipFillType + else: + e2FillType = self._ClipFillType + e2FillType2 = self._SubjFillType + + if e1FillType == PolyFillType.Positive: e1Wc = e1.windCnt + elif e1FillType == PolyFillType.Negative: e1Wc = -e1.windCnt + else: e1Wc = abs(e1.windCnt) + + if e2FillType == PolyFillType.Positive: e2Wc = e2.windCnt + elif e2FillType == PolyFillType.Negative: e2Wc = -e2.windCnt + else: e2Wc = abs(e2.windCnt) + + if e1Contributing and e2contributing: + if e1stops or e2stops or \ + not (e1Wc == 0 or e1Wc == 1) or not (e2Wc == 0 or e2Wc == 1) or \ + (e1.PolyType != e2.PolyType and self._ClipType != ClipType.Xor): + self._AddLocalMaxPoly(e1, e2, pt) + else: + _DoBothEdges(e1, e2, pt, self._PolyOutList) + elif e1Contributing: + if (e2Wc == 0 or e2Wc == 1) and \ + (self._ClipType != ClipType.Intersection or \ + e2.PolyType == PolyType.Subject or \ + e2.windCnt2 != 0): _DoEdge1(e1, e2, pt, self._PolyOutList) + elif e2contributing: + if (e1Wc == 0 or e1Wc == 1) and \ + (self._ClipType != ClipType.Intersection or \ + e1.PolyType == PolyType.Subject or \ + e1.windCnt2 != 0): _DoEdge2(e1, e2, pt, self._PolyOutList) + + elif (e1Wc == 0 or e1Wc == 1) and (e2Wc == 0 or e2Wc == 1) and \ + not e1stops and not e2stops: + + e1FillType2 = e2FillType2 = PolyFillType.EvenOdd + if e1FillType2 == PolyFillType.Positive: e1Wc2 = e1.windCnt2 + elif e1FillType2 == PolyFillType.Negative: e1Wc2 = -e1.windCnt2 + else: e1Wc2 = abs(e1.windCnt2) + if e2FillType2 == PolyFillType.Positive: e2Wc2 = e2.windCnt2 + elif e2FillType2 == PolyFillType.Negative: e2Wc2 = -e2.windCnt2 + else: e2Wc2 = abs(e2.windCnt2) + + if e1.PolyType != e2.PolyType: + self._AddLocalMinPoly(e1, e2, pt) + elif e1Wc == 1 and e2Wc == 1: + if self._ClipType == ClipType.Intersection: + if e1Wc2 > 0 and e2Wc2 > 0: + self._AddLocalMinPoly(e1, e2, pt) + elif self._ClipType == ClipType.Union: + if e1Wc2 <= 0 and e2Wc2 <= 0: + self._AddLocalMinPoly(e1, e2, pt) + elif self._ClipType == ClipType.Difference: + if (e1.PolyType == PolyType.Clip and e1Wc2 > 0 and e2Wc2 > 0) or \ + (e1.PolyType == PolyType.Subject and e1Wc2 <= 0 and e2Wc2 <= 0): + self._AddLocalMinPoly(e1, e2, pt) + else: + self._AddLocalMinPoly(e1, e2, pt) + else: + _SwapSides(e1, e2, self._PolyOutList) + + if e1stops != e2stops and \ + ((e1stops and e1.outIdx >= 0) or (e2stops and e2.outIdx >= 0)): + _SwapSides(e1, e2, self._PolyOutList) + _SwapPolyIndexes(e1, e2) + if e1stops: self._DeleteFromAEL(e1) + if e2stops: self._DeleteFromAEL(e2) + + def _DoMaxima(self, e, topY): + eMaxPair = _GetMaximaPair(e) + x = e.xTop + eNext = e.nextInAEL + while eNext != eMaxPair: + if eNext is None: raise Exception("DoMaxima error") + self._IntersectEdges(e, eNext, Point(x, topY), Protects.Both) + self._SwapPositionsInAEL(e, eNext) + eNext = eNext.nextInAEL + if e.outIdx < 0 and eMaxPair.outIdx < 0: + self._DeleteFromAEL(e) + self._DeleteFromAEL(eMaxPair) + elif e.outIdx >= 0 and eMaxPair.outIdx >= 0: + self._IntersectEdges(e, eMaxPair, Point(x, topY)) + else: + raise Exception("DoMaxima error") + + def _UpdateEdgeIntoAEL(self, e): + if e.nextInLML is None: + raise Exception("UpdateEdgeIntoAEL error") + aelPrev = e.prevInAEL + aelNext = e.nextInAEL + e.nextInLML.outIdx = e.outIdx + if aelPrev is not None: + aelPrev.nextInAEL = e.nextInLML + else: + self._ActiveEdges = e.nextInLML + if aelNext is not None: + aelNext.prevInAEL = e.nextInLML + e.nextInLML.side = e.side + e.nextInLML.windDelta = e.windDelta + e.nextInLML.windCnt = e.windCnt + e.nextInLML.windCnt2 = e.windCnt2 + e = e.nextInLML + e.prevInAEL = aelPrev + e.nextInAEL = aelNext + if e.dx != horizontal: + self._InsertScanbeam(e.yTop) + return e + + def _AddLocalMinPoly(self, e1, e2, pt): + if e2.dx == horizontal or e1.dx > e2.dx: + _AddOutPt(e1, pt, self._PolyOutList) + e2.outIdx = e1.outIdx + e1.side = EdgeSide.Left + e2.side = EdgeSide.Right + e = e1 + if e.prevInAEL == e2: prevE = e2.prevInAEL + else: prevE = e1.prevInAEL + else: + _AddOutPt(e2, pt, self._PolyOutList) + e1.outIdx = e2.outIdx + e1.side = EdgeSide.Right + e2.side = EdgeSide.Left + e = e2 + if e.prevInAEL == e1: prevE = e1.prevInAEL + else: prevE = e.prevInAEL + + if prevE is not None and prevE.outIdx >= 0 and \ + _TopX(prevE, pt.y) == _TopX(e, pt.y) and \ + _SlopesEqual2(e, prevE): + self._AddJoin(e, prevE) + + + def _AddLocalMaxPoly(self, e1, e2, pt): + _AddOutPt(e1, pt, self._PolyOutList) + if e1.outIdx == e2.outIdx: + e1.outIdx = -1 + e2.outIdx = -1 + elif e1.outIdx < e2.outIdx: + self._AppendPolygon(e1, e2) + else: + self._AppendPolygon(e2, e1) + + def _AppendPolygon(self, e1, e2): + outRec1 = self._PolyOutList[e1.outIdx] + outRec2 = self._PolyOutList[e2.outIdx] + holeStateRec = None + if _Param1RightOfParam2(outRec1, outRec2): holeStateRec = outRec2 + elif _Param1RightOfParam2(outRec2, outRec1): holeStateRec = outRec1 + else: holeStateRec = _GetLowermostRec(outRec1, outRec2) + + p1_lft = outRec1.pts + p2_lft = outRec2.pts + p1_rt = p1_lft.prevOp + p2_rt = p2_lft.prevOp + newSide = EdgeSide.Left + + if e1.side == EdgeSide.Left: + if e2.side == EdgeSide.Left: + # z y x a b c + _ReversePolyPtLinks(p2_lft) + p2_lft.nextOp = p1_lft + p1_lft.prevOp = p2_lft + p1_rt.nextOp = p2_rt + p2_rt.prevOp = p1_rt + outRec1.pts = p2_rt + else: + # x y z a b c + p2_rt.nextOp = p1_lft + p1_lft.prevOp = p2_rt + p2_lft.prevOp = p1_rt + p1_rt.nextOp = p2_lft + outRec1.pts = p2_lft + else: + newSide = EdgeSide.Right + if e2.side == EdgeSide.Right: + # a b c z y x + _ReversePolyPtLinks(p2_lft) + p1_rt.nextOp = p2_rt + p2_rt.prevOp = p1_rt + p2_lft.nextOp = p1_lft + p1_lft.prevOp = p2_lft + else: + # a b c x y z + p1_rt.nextOp = p2_lft + p2_lft.prevOp = p1_rt + p1_lft.prevOp = p2_rt + p2_rt.nextOp = p1_lft + + if holeStateRec == outRec2: + outRec1.bottomPt = outRec2.bottomPt + outRec1.bottomPt.idx = outRec1.idx + if outRec2.FirstLeft != outRec1: + outRec1.FirstLeft = outRec2.FirstLeft + outRec1.isHole = outRec2.isHole + outRec2.pts = None + outRec2.bottomPt = None + outRec2.FirstLeft = outRec1 + OKIdx = outRec1.idx + ObsoleteIdx = outRec2.idx + + e1.outIdx = -1 + e2.outIdx = -1 + + e = self._ActiveEdges + while e is not None: + if e.outIdx == ObsoleteIdx: + e.outIdx = OKIdx + e.side = newSide + break + e = e.nextInAEL + + if self._JoinList is not None: + for jr in self._JoinList: + if jr.poly1Idx == ObsoleteIdx: jr.poly1Idx = OKIdx + if jr.poly2Idx == ObsoleteIdx: jr.poly2Idx = OKIdx + + if self._HorzJoins is not None: + hj = self._HorzJoins + while True: + if hj.savedIdx == ObsoleteIdx: + hj.savedIdx = OKIdx + hj = hj.nextHj + if hj == self._HorzJoins: + break + + def _FixupIntersections(self): + if self._IntersectNodes.nextIn is None: return True + try: + self._CopyAELToSEL() + int1 = self._IntersectNodes + int2 = self._IntersectNodes.nextIn + e1 = e2 = None + while int2 is not None: + e1 = int1.e1 + if e1.prevInSEL == int1.e2: e2 = e1.prevInSEL + elif (e1.nextInSEL == int1.e2): e2 = e1.nextInSEL + else: + while int2 is not None: + if int2.e1.nextInSEL == int2.e2 or \ + int2.e1.prevInSEL == int2.e2: break + int2 = int2.nextIn + if int2 is None: + self._SortedEdges = None + return False + _SwapIntersectNodes(int1, int2) + e1 = int1.e1 + e2 = int1.e2 + self._SwapPositionsInSEL(e1, e2) + int1 = int1.nextIn + int2 = int1.nextIn + finally: + self._SortedEdges = None + return int1.e1.prevInSEL == int1.e2 or int1.e1.nextInSEL == int1.e2 + + def _ProcessEdgesAtTopOfScanbeam(self, topY): + e = self._ActiveEdges + while e is not None: + if _IsMaxima(e, topY) and _GetMaximaPair(e).dx != horizontal: + ePrev = e.prevInAEL + self._DoMaxima(e, topY) + if ePrev is None: e = self._ActiveEdges + else: e = ePrev.nextInAEL + else: + if _IsIntermediate(e, topY) and e.nextInLML.dx == horizontal: + if e.outIdx >= 0: + _AddOutPt(e, Point(e.xTop, e.yTop), self._PolyOutList) + + hj = self._HorzJoins + if hj is not None: + while True: + _1, _2, overlap = _GetOverlapSegment( + Point(hj.edge.xBot, hj.edge.yBot), + Point(hj.edge.xTop, hj.edge.yTop), + Point(e.nextInLML.XBot, e.nextInLML.yBot), + Point(e.nextInLML.xTop, e.nextInLML.yTop)) + if overlap: self._AddJoin(hj.edge, e.nextInLML, hj.savedIdx, e.outIdx) + hj = hj.nextHj + if hj == self._HorzJoins: break + self._AddHorzJoin(e.nextInLML, e.outIdx) + + e = self._UpdateEdgeIntoAEL(e) + self._AddEdgeToSEL(e) + else: + e.xCurr = _TopX(e, topY) + e.yCurr = topY + e = e.nextInAEL + + self._ProcessHorizontals() + + e = self._ActiveEdges + while e is not None: + if _IsIntermediate(e, topY): + if (e.outIdx >= 0) : + _AddOutPt(e, Point(e.xTop, e.yTop), self._PolyOutList) + e = self._UpdateEdgeIntoAEL(e) + + ePrev = e.prevInAEL + eNext = e.nextInAEL + if ePrev is not None and ePrev.xCurr == e.xBot and \ + (ePrev.yCurr == e.yBot) and (e.outIdx >= 0) and \ + (ePrev.outIdx >= 0) and (ePrev.yCurr > ePrev.yTop) and \ + _SlopesEqual2(e, ePrev): + _AddOutPt(ePrev, Point(e.xBot, e.yBot), self._PolyOutList) + self._AddJoin(e, ePrev) + elif eNext is not None and (eNext.xCurr == e.xBot) and \ + (eNext.yCurr == e.yBot) and (e.outIdx >= 0) and \ + (eNext.outIdx >= 0) and (eNext.yCurr > eNext.yTop) and \ + _SlopesEqual2(e, eNext): + _AddOutPt(eNext, Point(e.xBot, e.yBot), self._PolyOutList) + self._AddJoin(e, eNext) + + e = e.nextInAEL + + def _Area(self, pts): + # see http://www.mathopenref.com/coordpolygonarea2.html + result = 0.0 + p = pts + while True: + result += (p.pt.x + p.prevOp.pt.x) * (p.prevOp.pt.y - p.pt.y) + p = p.nextOp + if p == pts: break + return result / 2 + + def _JoinPoints(self, jr): + p1, p2 = None, None + outRec1 = self._PolyOutList[jr.poly1Idx] + outRec2 = self._PolyOutList[jr.poly2Idx] + if outRec1 is None or outRec2 is None: return p1, p2, False + pp1a = outRec1.pts; pp2a = outRec2.pts + pt1 = jr.pt2a; pt2 = jr.pt2b + pt3 = jr.pt1a; pt4 = jr.pt1b + pp1a, pt1, pt2, result = _FindSegment(pp1a, pt1, pt2) + if not result: return p1, p2, False + if (outRec1 == outRec2): + pp2a = pp1a.nextOp + pp2a, pt3, pt4, result = _FindSegment(pp2a, pt3, pt4) + if not result or pp2a == pp1a: return p1, p2, False + else: + pp2a, pt3, pt4, result = _FindSegment(pp2a, pt3, pt4) + if not result: return p1, p2, False + pt1, pt2, result = _GetOverlapSegment(pt1, pt2, pt3, pt4) + if not result: return p1, p2, False + + prevOp = pp1a.prevOp + if _PointsEqual(pp1a.pt, pt1): p1 = pp1a + elif _PointsEqual(prevOp.pt, pt1): p1 = prevOp + else: p1 = _InsertPolyPtBetween(pp1a, prevOp, pt1) + + if _PointsEqual(pp1a.pt, pt2): p2 = pp1a + elif _PointsEqual(prevOp.pt, pt2): p2 = prevOp + elif (p1 == pp1a) or (p1 == prevOp): + p2 = _InsertPolyPtBetween(pp1a, prevOp, pt2) + elif _Pt3IsBetweenPt1AndPt2(pp1a.pt, p1.pt, pt2): + p2 = _InsertPolyPtBetween(pp1a, p1, pt2) + else: p2 = _InsertPolyPtBetween(p1, prevOp, pt2) + + prevOp = pp2a.prevOp + if _PointsEqual(pp2a.pt, pt1): p3 = pp2a + elif _PointsEqual(prevOp.pt, pt1): p3 = prevOp + else: p3 = _InsertPolyPtBetween(pp2a, prevOp, pt1) + if _PointsEqual(pp2a.pt, pt2): p4 = pp2a + elif _PointsEqual(prevOp.pt, pt2): p4 = prevOp + elif (p3 == pp2a) or (p3 == prevOp): + p4 = _InsertPolyPtBetween(pp2a, prevOp, pt2) + elif _Pt3IsBetweenPt1AndPt2(pp2a.pt, p3.pt, pt2): + p4 = _InsertPolyPtBetween(pp2a, p3, pt2) + else: p4 = _InsertPolyPtBetween(p3, prevOp, pt2) + + if p1.nextOp == p2 and p3.prevOp == p4: + p1.nextOp = p3 + p3.prevOp = p1 + p2.prevOp = p4 + p4.nextOp = p2 + return p1, p2, True + elif p1.prevOp == p2 and p3.nextOp == p4: + p1.prevOp = p3 + p3.nextOp = p1 + p2.nextOp = p4 + p4.prevOp = p2 + return p1, p2, True + return p1, p2, False + + def _FixupFirstLefts1(self, oldOutRec, newOutRec): + for outRec in self._PolyOutList: + if outRec.pts is not None and outRec.FirstLeft == oldOutRec: + if _Poly2ContainsPoly1(outRec.pts, newOutRec.pts): + outRec.FirstLeft = newOutRec + + def _FixupFirstLefts2(self, oldOutRec, newOutRec): + for outRec in self._PolyOutList: + if outRec.FirstLeft == oldOutRec: outRec.FirstLeft = newOutRec + + def _JoinCommonEdges(self): + for i in range(len(self._JoinList)): + jr = self._JoinList[i] + outRec1 = self._PolyOutList[jr.poly1Idx] + outRec2 = self._PolyOutList[jr.poly2Idx] + if outRec1.pts is None or outRec2.pts is None: continue + + if outRec1 == outRec2: holeStateRec = outRec1 + elif _Param1RightOfParam2(outRec1, outRec2): holeStateRec = outRec2 + elif _Param1RightOfParam2(outRec2, outRec1): holeStateRec = outRec1 + else: holeStateRec = _GetLowermostRec(outRec1, outRec2) + + p1, p2, result = self._JoinPoints(jr) + if not result: continue + + if outRec1 == outRec2: + outRec1.pts = _GetBottomPt(p1) + outRec1.bottomPt = outRec1.pts + outRec1.bottomPt.idx = outRec1.idx + outRec2 = OutRec(len(self._PolyOutList)) + self._PolyOutList.append(outRec2) + jr.poly2Idx = outRec2.idx + outRec2.pts = _GetBottomPt(p2) + outRec2.bottomPt = outRec2.pts + outRec2.bottomPt.idx = outRec2.idx + + if _Poly2ContainsPoly1(outRec2.pts, outRec1.pts): + outRec2.isHole = not outRec1.isHole + outRec2.FirstLeft = outRec1 + + self._FixupJoinRecs(jr, p2, i + 1) + + if self._UsingPolyTree: self._FixupFirstLefts2(outRec2, outRec1) + + _FixupOutPolygon(outRec1) + _FixupOutPolygon(outRec2) + + if outRec2.isHole == self._Area(outRec2) > 0.0: + _ReversePolyPtLinks(outRec2.pts) + + elif _Poly2ContainsPoly1(outRec1.pts, outRec2.pts): + outRec2.isHole = outRec1.isHole + outRec1.isHole = not outRec2.isHole + outRec2.FirstLeft = outRec1.FirstLeft + outRec1.FirstLeft = outRec2 + + self._FixupJoinRecs(jr, p2, i + 1) + + if self._UsingPolyTree: self._FixupFirstLefts2(outRec1, outRec2) + + _FixupOutPolygon(outRec1) + _FixupOutPolygon(outRec2) + + if outRec1.isHole == self._Area(outRec1) > 0.0: + _ReversePolyPtLinks(outRec1.pts) + else: + outRec2.isHole = outRec1.isHole + outRec2.FirstLeft = outRec1.FirstLeft + + self._FixupJoinRecs(jr, p2, i + 1) + if self._UsingPolyTree: self._FixupFirstLefts1(outRec1, outRec2) + + _FixupOutPolygon(outRec1) + _FixupOutPolygon(outRec2) + else: + _FixupOutPolygon(outRec1) + OKIdx = outRec1.idx + ObsoleteIdx = outRec2.idx + outRec2.pts = None + outRec2.bottomPt = None + + outRec1.isHole = holeStateRec.isHole + if holeStateRec == outRec2: + outRec1.FirstLeft = outRec2.FirstLeft + outRec2.FirstLeft = outRec1 + + for j in range(i +1, len(self._JoinList)): + jr2 = self._JoinList[j] + if (jr2.poly1Idx == ObsoleteIdx): jr2.poly1Idx = OKIdx + if (jr2.poly2Idx == ObsoleteIdx): jr2.poly2Idx = OKIdx + if self._UsingPolyTree: self._FixupFirstLefts2(outRec2, outRec1) + + def _ExecuteInternal(self): + try: + try: + self._Reset() + if self._Scanbeam is None: return True + botY = self._PopScanbeam() + while self._Scanbeam is not None: + self._InsertLocalMinimaIntoAEL(botY) + self._HorzJoins = None + self._ProcessHorizontals() + topY = self._PopScanbeam() + if not self._ProcessIntersections(botY, topY): return False + self._ProcessEdgesAtTopOfScanbeam(topY) + botY = topY + + for outRec in self._PolyOutList: + if outRec.pts is None: continue + _FixupOutPolygon(outRec) + if outRec.pts is None: continue + if outRec.isHole == (self._Area(outRec.pts) > 0.0): + _ReversePolyPtLinks(outRec.pts) + + if self._JoinList is not None: self._JoinCommonEdges() + return True + finally: + self._JoinList = None + self._HorzJoins = None + except: + return False + + def Execute( + self, + clipType, + solution, + subjFillType = PolyFillType.EvenOdd, + clipFillType = PolyFillType.EvenOdd): + if self._ExecuteLocked: return False + try: + self._ExecuteLocked = True + self._UsingPolyTree = True + del solution[:] + self._SubjFillType = subjFillType + self._ClipFillType = clipFillType + self._ClipType = clipType + result = self._ExecuteInternal() + if result: self._BuildResult(solution) + finally: + self._ExecuteLocked = False + self._UsingPolyTree = False + return result + + def Execute2( + self, + clipType, + solutionTree, + subjFillType = PolyFillType.EvenOdd, + clipFillType = PolyFillType.EvenOdd): + if self._ExecuteLocked: return False + try: + self._ExecuteLocked = True + self._UsingPolyTree = True + solutionTree.Clear() + self._SubjFillType = subjFillType + self._ClipFillType = clipFillType + self._ClipType = clipType + result = self._ExecuteInternal() + if result: self._BuildResult2(solutionTree) + finally: + self._ExecuteLocked = False + self._UsingPolyTree = False + return result + + def _BuildResult(self, polygons): + for outRec in self._PolyOutList: + if outRec is None: continue + cnt = _PointCount(outRec.pts) + if (cnt < 3): continue + poly = [] + op = outRec.pts + for _ in range(cnt): + poly.append(Point(op.pt.x, op.pt.y)) + op = op.prevOp + polygons.append(poly) + return + + def _BuildResult2(self, polyTree): + for outRec in self._PolyOutList: + if outRec is None: continue + cnt = _PointCount(outRec.pts) + if (cnt < 3): continue + _FixHoleLinkage(outRec) + + # add nodes to _AllNodes list ... + polyNode = PolyNode() + polyTree._AllNodes.append(polyNode) + outRec.PolyNode = polyNode + op = outRec.pts + while True: + polyNode.Contour.append(op.pt) + op = op.prevOp + if op == outRec.pts: break + + # build the tree ... + for outRec in self._PolyOutList: + if outRec.PolyNode is None: continue + if outRec.FirstLeft is None: + polyTree._AddChild(outRec.PolyNode) + else: + outRec.FirstLeft.PolyNode._AddChild(outRec.PolyNode) + + return + +#=============================================================================== +# OffsetPolygons (+ ancilliary functions) +#=============================================================================== + +FloatPoint = namedtuple('FloatPoint', 'x y') +Rect = namedtuple('FloatPoint', 'left top right bottom') + +def _GetUnitNormal(pt1, pt2): + if pt2.x == pt1.x and pt2.y == pt1.y: + return FloatPoint(0.0, 0.0) + dx = pt2.x - pt1.x + dy = pt2.y - pt1.y + f = 1.0 / math.hypot(dx, dy) + dx = float(dx) * f + dy = float(dy) * f + return FloatPoint(dy, -dx) + +def _BuildArc(pt, a1, a2, r): + steps = max(6, round(math.sqrt(abs(r)) * abs(a2 - a1))) + if steps > 0x100: steps = 0x100 + result = [] + n = steps - 1 + d = (a2 - a1) / n + a = a1 + for _ in range(n): + s = math.sin(a) + c = math.cos(a) + result.append(FloatPoint(pt.x + round(c * r), pt.y + round(s * r))) + a += d + return result + +def _GetBounds(pts): + left = None + for poly in pts: + for pt in poly: + left = pt.x + top = pt.y + right = pt.x + bottom = pt.y + break + break + + for poly in pts: + for pt in poly: + if pt.x < left: left = pt.x + if pt.x > right: right = pt.x + if pt.y < top: top = pt.y + if pt.y > bottom: bottom = pt.y + if left is None: return Rect(0, 0, 0, 0) + else: return Rect(left, top, right, bottom) + +def _GetLowestPt(poly): + # precondition: poly must not be empty + result = poly[0] + for pt in poly: + if pt.y > result.y or (pt.y == result.y and pt.x < result.x): + result = pt + return result + +def _StripDupPts(poly): + if poly == []: return poly + for i in range(1, len(poly)): + if _PointsEqual(poly[i-1], poly[i]): poly.pop(i) + i = len(poly) -1 + while i > 0 and _PointsEqual(poly[i], poly[0]): + poly.pop(i) + i -= 1 + return poly + +def OffsetPolygons(polys, delta, jointype = JoinType.Square, miterLimit= 2.0, autoFix = True): + + def DoSquare(pt, mul = 1.0): + pt1 = Point(round(pt.x + Normals[k].x * delta), round(pt.y + Normals[k].y * delta)) + pt2 = Point(round(pt.x + Normals[j].x * delta), round(pt.y + Normals[j].y * delta)) + if (Normals[k].x*Normals[j].y-Normals[j].x*Normals[k].y) * delta >= 0: + a1 = math.atan2(Normals[k].y, Normals[k].x) + a2 = math.atan2(-Normals[j].y, -Normals[j].x) + a1 = abs(a2 - a1); + if a1 > math.pi: a1 = math.pi * 2 - a1 + dx = math.tan((math.pi - a1)/4) * abs(delta*mul) + + pt1 = Point(round(pt1.x -Normals[k].y * dx), round(pt1.y + Normals[k].x * dx)) + result.append(pt1) + pt2 = Point(round(pt2.x + Normals[j].y * dx), round(pt2.y - Normals[j].x * dx)) + result.append(pt2) + else: + result.append(pt1) + result.append(pt) + result.append(pt2) + + def DoMiter(pt): + if ((Normals[k].x* Normals[j].y - Normals[j].x * Normals[k].y) * delta >= 0): + q = delta / r; + result.append(Point(round(pt.x + (Normals[k].x + Normals[j].x) *q), + round(pt.y + (Normals[k].y + Normals[j].y) *q))) + else: + pt1 = Point(round(pt.x + Normals[k].x * delta), \ + round(pt.y + Normals[k].y * delta)) + pt2 = Point(round(pt.x + Normals[j].x * delta), \ + round(pt.y + Normals[j].y * delta)) + result.append(pt1) + result.append(pt) + result.append(pt2) + + def DoRound(pt): + pt1 = Point(round(pt.x + Normals[k].x * delta), \ + round(pt.y + Normals[k].y * delta)) + pt2 = Point(round(pt.x + Normals[j].x * delta), \ + round(pt.y + Normals[j].y * delta)) + result.append(pt1) + if (Normals[k].x*Normals[j].y - Normals[j].x*Normals[k].y)*delta >= 0: + if (Normals[j].x * Normals[k].x + Normals[j].y * Normals[k].y) < 0.985: + a1 = math.atan2(Normals[k].y, Normals[k].x) + a2 = math.atan2(Normals[j].y, Normals[j].x) + if (delta > 0) and (a2 < a1): a2 = a2 + math.pi * 2 + elif (delta < 0) and (a2 > a1): a2 = a2 - math.pi * 2 + arc = _BuildArc(pt, a1, a2, delta) + result.extend(arc) + else: + result.append(pt) + result.append(pt2) + + ppts = polys[:] + + if autoFix: + botPoly = None + botPt = None + for poly in ppts: + poly = _StripDupPts(poly) + if len(poly) < 3: continue + bot = _GetLowestPt(poly) + if botPt is None or (bot.y > botPt.y) or \ + (bot.y == botPt.y and bot.x < botPt.x): + botPt = bot + botPoly = poly + if botPt is None: return ppts + # if the outermost polygon has the wrong orientation, + # reverse the orientation of all the polygons ... + if Area(botPoly) < 0.0: + for poly in ppts: + poly = poly[::-1] + else: + # make sure that polygon's start & end pts don't match ... + for poly in ppts: + i = len(poly) -1 + while i > 0 and _PointsEqual(poly[i], poly[0]): + poly.pop(i) + i -= 1 + if len(poly) < 3: poly = [] + + + if miterLimit <= 1: miterLimit = 1.0 + rmin = 2.0/(miterLimit * miterLimit) + + res = [] + for pts in ppts: + Normals = [] + result = [] + cnt = len(pts) + for j in range(cnt -1): + Normals.append(_GetUnitNormal(pts[j], pts[j+1])) + Normals.append(_GetUnitNormal(pts[cnt-1], pts[0])) + + k = cnt -1 + for j in range(cnt): + if jointype == JoinType.Miter: + r = 1.0 + (Normals[j].x * Normals[k].x + Normals[j].y * Normals[k].y) + if (r >= rmin): DoMiter(pts[j]) + else: DoSquare(pts[j], miterLimit) + elif jointype == JoinType.Square: DoSquare(pts[j]) + else: DoRound(pts[j]) + k = j + res.append(result) + + c = Clipper() + c.AddPolygons(res, PolyType.Subject) + if delta > 0: + c.Execute(ClipType.Union, res, PolyFillType.Positive, PolyFillType.Positive) + else: + bounds = _GetBounds(res) + outer = [] + outer.append(Point(bounds.left-10, bounds.bottom+10)) + outer.append(Point(bounds.right+10, bounds.bottom+10)) + outer.append(Point(bounds.right+10, bounds.top-10)) + outer.append(Point(bounds.left-10, bounds.top-10)) + c.AddPolygon(outer, PolyType.Subject) + c.Execute(ClipType.Union, res, PolyFillType.Negative, PolyFillType.Negative) + res.pop(0) + for poly in res: + poly = poly[::-1] + return res + +def CleanPolygon(poly, distance = 1.415): + cnt = len(poly) + if (cnt < 3): return [] + result = [] + d = round(distance * distance) + ip = poly[cnt -1] + for i in range(cnt): + if ((poly[i].x - ip.x) * (poly[i].x - ip.x) + \ + (poly[i].y - ip.y) * (poly[i].y - ip.y) <= d): + continue + result.append(poly[i]) + ip = poly[i] + return result + +def CleanPolygons(polys, distance = 1.415): + result = [] + for poly in polys: + result.append(CleanPolygon(poly, distance = 1.415)) + return result diff --git a/clipper/python/clipper_demo.py b/clipper/python/clipper_demo.py new file mode 100755 index 0000000..14696aa --- /dev/null +++ b/clipper/python/clipper_demo.py @@ -0,0 +1,267 @@ +# from clipper import Area, Clipper, Point, ClipType, PolyType, PolyFillType +from clipper import * +import math +import re +from random import randint +# from subprocess import call +import os + +#=============================================================================== +#=============================================================================== + +def LoadFile1(lines): + # File type 1: first line is total polygons count and subsequent lines + # contain the polygon vertex count followed by its coords + try: + polygons = [] + poly = [] + for l in lines: + vals = re.split(' |, |,', l.strip()) + if len(vals) < 2: + if (len(poly) > 2): + polygons.append(poly) + poly = [] + else: + poly.append(Point(int(vals[0]), int(vals[1]))) + if (len(poly) > 2): + polygons.append(poly) + return polygons + except: + return None +#=============================================================================== + +def LoadFile2(lines): + # File type 2: vertex coords on consecutive lines for each polygon + # where each polygon is separated by an empty line + try: + polygons = [] + poly = [] + for l in lines: + l = l.strip() + if (l == ''): + if (len(poly) > 2): + polygons.append(poly) + poly = [] + else: + vals = re.split(' |, |,', l) + poly.append(Point(int(vals[0]), int(vals[1]))) + if (len(poly) > 2): + polygons.append(poly) + return polygons + except: + return None +#=============================================================================== + +def LoadFile(filename): + try: + f = open(filename, 'r') + try: + lines = f.readlines() + finally: + f.close() + # pick file type from format of first line ... + if len(lines) == 0: return [] + elif not ',' in lines[0]: return LoadFile1(lines) + else: return LoadFile2(lines) + except: + return None +#=============================================================================== + +def SaveToFile(filename, polys, scale = 1.0): + invScale = 1.0 / scale + try: + f = open(filename, 'w') + try: + if invScale == 1: + for poly in polys: + for pt in poly: + f.write("{0}, {1}\n".format(pt.x, pt.y)) + f.write("\n") + else: + for poly in polys: + for pt in poly: + f.write("{0:.4f}, {1:.4f}\n".format(pt.x * invScale, pt.y * invScale)) + f.write("\n") + finally: + f.close() + except: + return +#=============================================================================== + +def RandomPoly(maxWidth, maxHeight, vertCnt): + result = [] + for _ in range(vertCnt): + result.append(Point(randint(0, maxWidth), randint(0, maxHeight))) + return result + +#=============================================================================== +# SVGBuilder +#=============================================================================== +class SVGBuilder(object): + + def HtmlColor(self, val): + return "#{0:06x}".format(val & 0xFFFFFF) + + def AlphaClr(self, val): + return "{0:.2f}".format(float(val >> 24)/255) + + class StyleInfo(object): + def __init__(self): + self.fillType = PolyFillType.EvenOdd + self.brushClr = 0 + self.penClr = 0 + self.penWidth = 0.8 + self.showCoords = False + + class StyleInfoPlus(StyleInfo): + + def __init__(self): + SVGBuilder.StyleInfo.__init__(self) + self.polygons = [] + self.textlines = [] + + def __init__(self): + self.GlobalStyle = SVGBuilder.StyleInfo() + self.PolyInfoList = [] + self.PathHeader = " \n\n" + self.Header = """ + +\n + + + + + + + + + \n\n""" + + def AddPolygon(self, poly, brushColor, penColor): + if poly is None or len(poly) == 0: return + pi = self.StyleInfoPlus() + pi.penWidth = self.GlobalStyle.penWidth + pi.fillType = self.GlobalStyle.fillType + pi.showCoords = self.GlobalStyle.showCoords + pi.brushClr = brushColor + pi.penClr = penColor + pi.polygons.append(poly) + self.PolyInfoList.append(pi) + + def AddPolygons(self, polys, brushColor, penColor): + if polys is None or len(polys) == 0: return + pi = self.StyleInfoPlus() + pi.penWidth = self.GlobalStyle.penWidth + pi.fillType = self.GlobalStyle.fillType + pi.showCoords = self.GlobalStyle.showCoords + pi.brushClr = brushColor + pi.penClr = penColor + pi.polygons = polys + self.PolyInfoList.append(pi) + + def SaveToFile(self, filename, invScale = 1.0, margin = 10): + if len(self.PolyInfoList) == 0: return False + if invScale == 0: invScale = 1.0 + if margin < 0: margin = 0 + pi = self.PolyInfoList[0] + # get bounding rect ... + left = right = pi.polygons[0][0].x + top = bottom = pi.polygons[0][0].y + for pi in self.PolyInfoList: + for p in pi.polygons: + for ip in p: + if ip.x < left: left = ip.x + if ip.x > right: right = ip.x + if ip.y < top: top = ip.y + if ip.y > bottom: bottom = ip.y + left *= invScale + top *= invScale + right *= invScale + bottom *= invScale + offsetX = -left + margin + offsetY = -top + margin + + f = open(filename, 'w') + m2 = margin * 2 + f.write(self.Header.format(right - left + m2, bottom - top + m2)) + for pi in self.PolyInfoList: + f.write(self.PathHeader) + for p in pi.polygons: + cnt = len(p) + if cnt < 3: continue + f.write(" M {0:.2f} {1:.2f}".format(p[0].x * invScale + offsetX, p[0].y * invScale + offsetY)) + for i in range(1,cnt): + f.write(" L {0:.2f} {1:.2f}".format(p[i].x * invScale + offsetX, p[i].y * invScale + offsetY)) + f.write(" z") + fillRule = "evenodd" + if pi.fillType != PolyFillType.EvenOdd: fillRule = "nonzero" + f.write(self.PathFooter.format(self.HtmlColor(pi.brushClr), + self.AlphaClr(pi.brushClr), fillRule, + self.HtmlColor(pi.penClr), self.AlphaClr(pi.penClr), pi.penWidth)) + + if (pi.showCoords): + f.write("\n\n") + for p in pi.polygons: + cnt = len(p) + if cnt < 3: continue + for pt in p: + x = pt.x * invScale + offsetX + y = pt.y * invScale + offsetY + f.write("{2},{3}\n".format(x, y, pt.x, pt.y)) + f.write("\n") + f.write("\n") + + f.write("\n") + f.close() + return True + +#=============================================================================== +# Main entry ... +#=============================================================================== + +scaleExp = 0 +scale = math.pow(10, scaleExp) +invScale = 1.0 / scale + +subj, clip = [], [] +#load saved subject and clip polygons ... +#subj = LoadFile('./subj.txt') +#clip = LoadFile('./clip.txt') + +# Generate random subject and clip polygons ... +subj.append(RandomPoly(640 * scale, 480 * scale, 100)) +clip.append(RandomPoly(640 * scale, 480 * scale, 100)) +#SaveToFile('./subj2.txt', subj, scale) +#SaveToFile('./clip2.txt', clip, scale) + +# Load the polygons into Clipper and execute the boolean clip op ... +c = Clipper() +solution = [] +pft = PolyFillType.EvenOdd + +c.AddPolygons(subj, PolyType.Subject) +c.AddPolygons(clip, PolyType.Clip) +result = c.Execute(ClipType.Intersection, solution, pft, pft) + +SaveToFile('./solution2.txt', solution, scale) + +# Create an SVG file to display what's happened ... +svgBuilder = SVGBuilder() +#svgBuilder.GlobalStyle.showCoords = True +svgBuilder.GlobalStyle.fillType = pft +svgBuilder.AddPolygons(subj, 0x402020FF, 0x802020FF) +#svgBuilder.GlobalStyle.showCoords = False +svgBuilder.AddPolygons(clip, 0x40FFFF20, 0x80FF2020) +svgBuilder.GlobalStyle.penWidth = 0.6 +svgBuilder.AddPolygons(solution, 0x60138013, 0xFF003300) + +holes = [] +for poly in solution: + if Area(poly) < 0: holes.append(poly) +svgBuilder.AddPolygons(holes, 0x0, 0xFFFF0000) +svgBuilder.SaveToFile('./test.svg', invScale, 100) + +if result: os.startfile('test.svg') # call(('open', 'test.svg')) # print("finished") # +else: print("failed") \ No newline at end of file diff --git a/clipper/python/python_readme.txt b/clipper/python/python_readme.txt new file mode 100755 index 0000000..8d33adc --- /dev/null +++ b/clipper/python/python_readme.txt @@ -0,0 +1,9 @@ + +The clipper.py file included in this distribution is a Python translation of the Clipper Library. +Because the Python code is interpreted (ie not compiled) it is about 100 times slower than compiled versions of Clipper. +(Even when using the PyPy Just-In-Time compiler the code is still about 50 times slower.) + +Alternatively, Maxime Chalon has written a Python extension module for Clipper: +https://sites.google.com/site/maxelsbackyard/home/pyclipper +This module provides a Python interface to the C++ compiled Clipper Library (and runs about 100 times faster than clipper.py). + diff --git a/clipper/ruby/ruby_readme.txt b/clipper/ruby/ruby_readme.txt new file mode 100755 index 0000000..c71c1d8 --- /dev/null +++ b/clipper/ruby/ruby_readme.txt @@ -0,0 +1,4 @@ +A Ruby module written by Mike Owens +that wraps the Clipper library can be downloaded from: + +http://github.com/mieko/rbclipper \ No newline at end of file diff --git a/computeOverlap.m b/computeOverlap.m new file mode 100755 index 0000000..f3cff65 --- /dev/null +++ b/computeOverlap.m @@ -0,0 +1,119 @@ +function overlaps = computeOverlap(boxes1, boxes2, mode) +% Computes the overlap between two sets of boxes +% overlaps is n x m where n is the number of boxes in boxes1 +% +% mode: Three possible modes +% pascal: intersection / union +% pedro : intersection / first box area +% wrtmin: intersection / min of two areas +% +% Author: saurabh.me@gmail.com (Saurabh Singh). + +switch mode + case 'pascal' + overlaps = computePascalOverlap(boxes1, boxes2); + case 'pedro' + overlaps = computePedroOverlap(boxes1, boxes2); + case 'wrtmin' + overlaps = computeWrtMinOverlap(boxes1, boxes2); + otherwise + error('Unrecognized mode'); +end +end + +function overlaps = computePascalOverlap(boxes1, boxes2) +overlaps = zeros(size(boxes1, 1), size(boxes2, 1)); +if isempty(boxes1) + overlaps = []; +else + x11 = boxes1(:,1); + y11 = boxes1(:,2); + x12 = boxes1(:,3); + y12 = boxes1(:,4); + areab1 = (x12-x11+1) .* (y12-y11+1); + x21 = boxes2(:,1); + y21 = boxes2(:,2); + x22 = boxes2(:,3); + y22 = boxes2(:,4); + areab2 = (x22-x21+1) .* (y22-y21+1); + + for i = 1 : size(boxes1, 1) + for j = 1 : size(boxes2, 1) + xx1 = max(x11(i), x21(j)); + yy1 = max(y11(i), y21(j)); + xx2 = min(x12(i), x22(j)); + yy2 = min(y12(i), y22(j)); + w = xx2-xx1+1; + h = yy2-yy1+1; + if w > 0 && h > 0 + overlaps(i, j) = w * h / (areab1(i) + areab2(j) - w * h); + end + end + end +end +end + +function overlaps = computePedroOverlap(boxes1, boxes2) +overlaps = zeros(size(boxes1, 1), size(boxes2, 1)); +if isempty(boxes1) + overlaps = []; +else + x1 = boxes1(:,1); + y1 = boxes1(:,2); + x2 = boxes1(:,3); + y2 = boxes1(:,4); + area = (x2-x1+1) .* (y2-y1+1); + + for i = 1 : size(boxes1, 1) + for j = 1 : size(boxes2, 1) + x21 = boxes2(j,1); + y21 = boxes2(j,2); + x22 = boxes2(j,3); + y22 = boxes2(j,4); + + xx1 = max(x1(i), x21); + yy1 = max(y1(i), y21); + xx2 = min(x2(i), x22); + yy2 = min(y2(i), y22); + w = xx2-xx1+1; + h = yy2-yy1+1; + if w > 0 && h > 0 + overlaps(i, j) = w * h / area(i); + end + end + end +end +end + +function overlaps = computeWrtMinOverlap(boxes1, boxes2) +% Overlap is intersection/min-area +overlaps = zeros(size(boxes1, 1), size(boxes2, 1)); +if isempty(boxes1) + overlaps = []; +else + x11 = boxes1(:,1); + y11 = boxes1(:,2); + x12 = boxes1(:,3); + y12 = boxes1(:,4); + areab1 = (x12-x11+1) .* (y12-y11+1); + x21 = boxes2(:,1); + y21 = boxes2(:,2); + x22 = boxes2(:,3); + y22 = boxes2(:,4); + areab2 = (x22-x21+1) .* (y22-y21+1); + + for i = 1 : size(boxes1, 1) + for j = 1 : size(boxes2, 1) + xx1 = max(x11(i), x21(j)); + yy1 = max(y11(i), y21(j)); + xx2 = min(x12(i), x22(j)); + yy2 = min(y12(i), y22(j)); + w = xx2-xx1+1; + h = yy2-yy1+1; + if w > 0 && h > 0 + overlaps(i, j) = w * h / min(areab1(i), areab2(j)); + end + end + end +end +end diff --git a/constructFeaturePyramid.m b/constructFeaturePyramid.m new file mode 100755 index 0000000..fde470a --- /dev/null +++ b/constructFeaturePyramid.m @@ -0,0 +1,11 @@ +function pyramid = constructFeaturePyramid(data, params, levels) +% levels: What level of pyramid to compute the features for. +% +% Author: saurabh.me@gmail.com (Saurabh Singh). + +if nargin < 3 + levels = []; +end +%imPaths = getImagePaths(data, imgsHome); +pyramid = constructFeaturePyramidForImg(data, params, levels); +end diff --git a/constructFeaturePyramidForImg.m b/constructFeaturePyramidForImg.m new file mode 100755 index 0000000..e5db08c --- /dev/null +++ b/constructFeaturePyramidForImg.m @@ -0,0 +1,105 @@ +function pyramid = constructFeaturePyramidForImg(im, params, levels) +% levels: What level of pyramid to compute the features for. +% +% Author: saurabh.me@gmail.com (Saurabh Singh). +sBins = params.sBins; +'constructfeatpyr' +size(im) +I = im; +if(dsfield(params,'imageCanonicalSize')) + canonicalSize = params.imageCanonicalSize; + [IS, canoScale] = convertToCanonicalSize(I, canonicalSize); +else + canonicalSize=min(size(I(:,:,1))); + canoScale=1; + IS=I; +end +size(IS) +[rows, cols, chans] = size(IS); +if chans < 3 + I = repmat(I, [1 1 3]); + fprintf('WARNING: Image has < 3 channels, replicating channels\n'); +end + +numLevels = getNumPyramidLevels(rows, cols, params.scaleIntervals, ... + params.patchCanonicalSize) +scales = getLevelScales(numLevels, params.scaleIntervals); +if nargin < 3 || isempty(levels) + levels = 1 : numLevels; +end +if(dsbool(params,'useColor')) + im2=RGB2Lab(im).*.0025; + if(dsbool(params,'extraColor')) + im2=im2.*25; + end +end +if(dsbool(params,'useColorHists')) + im2=RGB2Lab(im); +end +if(dsbool(params,'patchOnly')) + im2=rgb2gray(im); +end +pyramidLevs = cell(1, numLevels); +histbin=-100:20:100; +histbin(end)=histbin(end)+1; +gradientLevs={}; +for i = 1 : length(levels) + lev = levels(i); + I1 = imresize(I, canoScale / scales(lev),'bilinear'); + [nrows, ncols, unused_dims] = size(I1); + rowRem = rem(nrows, sBins); + colRem = rem(ncols, sBins); + if rowRem > 0 || colRem > 0 + I1 = I1(1:nrows-rowRem, 1:ncols-colRem, :); + end + if(~dsbool(params,'patchOnly')) + if(dsbool(params,'overallnorm')) + feat=features_nonorm(I1,sBins)*.02; + else + feat = features(I1, sBins); + end + [rows,cols,~]=size(feat); + else + rows=round(size(im2,1)/sBins); + cols=round(size(im2,2)/sBins); + end + if(dsbool(params,'patchOnly')) + feat=imresize(im2,[rows cols],'bilinear'); + else + feat=feat(:,:,1:31); + if(dsbool(params,'maxmin')) + feat=cat(3,feat(:,:,1:18),max(feat(:,:,1:9),feat(:,:,10:18)),... + min(feat(:,:,1:9),feat(:,:,10:18)),feat(:,:,28:end)); + end + if(dsbool(params,'medrat')) + f=features_medrat(I1,sBins)+.000001; + feat=cat(3,feat,f(2:end-1,2:end-1,:)); + end + if(dsbool(params,'useColor')) + feat=cat(3,feat,imresize(im2(:,:,2),[rows cols],'bilinear'),imresize(im2(:,:,3),[rows cols],'bilinear')); + elseif(dsbool(params,'useColorHists')) + for(k=2:3) + tohist=im2col(imresize(im2(:,:,k),[rows*sBins, cols*sBins]),[sBins,sBins],'distinct'); + fhist{k-1}=reshape(permute(histc(tohist,histbin,1),[2,1]),rows,cols,[]); + fhist{k-1}=fhist{k-1}(:,:,1:end-1).*.0006; + end + feat=cat(3,feat,fhist{1},fhist{2}); + end + end + [GX, GY] = gradient(I1); + GI = mean((GX*255).^2, 3) + mean((GY*255).^2, 3); + GI=imresize(GI,[rows,cols],'bilinear'); + pyramidLevs{lev} = feat;%(:, :, 1:31); + gradientLevs{lev} = GI; +end +canoSize.nrows = size(im,1); +canoSize.ncols = size(im,2); +pyramid = struct('features', {pyramidLevs}, 'scales', scales, ... + 'canonicalScale', canoScale, 'sbins', sBins, 'canonicalSize', canoSize, 'gradimg', {gradientLevs}); +end + +function numLev = getNumPyramidLevels(rows, cols, intervals, basePatSize) +lev1 = floor(intervals * log2(rows / basePatSize(1))); +lev2 = floor(intervals * log2(cols / basePatSize(2))); +numLev = min(lev1, lev2) + 1; +end diff --git a/det2mat.m b/det2mat.m new file mode 100755 index 0000000..862cde7 --- /dev/null +++ b/det2mat.m @@ -0,0 +1,26 @@ +function res=det2mat(dets); + if(isempty(dets)) + res=[]; + return; + end + dets=dets(:); + dim=7; + if(isfield(dets,'flip')) + dim=8; + end + if(isfield(dets,'boxid')) + dim=9; + end + + res=zeros(numel(dets),dim); + res(:,6)=[dets.detector]; + res(:,7)=[dets.imidx]; + if(isfield(dets,'flip')) + res(:,8)=[dets.flip]; + end + if(isfield(dets,'boxid')) + res(:,9)=[dets.boxid]; + end + pos=[dets.pos]; + res(:,1:5)=getBoxesForPedro(pos,[dets.decision]); +end diff --git a/detectInIm.m b/detectInIm.m new file mode 100755 index 0000000..4b42cd9 --- /dev/null +++ b/detectInIm.m @@ -0,0 +1,32 @@ +% Run detection on an image. model is the standard detector format-- +% i.e. it has a 'w' field where each row is a detector weight vector, +% a 'b' field with a bias that's subtracted from the score for each patch, +% and the 'id' field is an id for the detector. the image is the image +% at index imid in .ds.imgs{ds.conf.currimset}. conf can include: +% - 'flipall': flip the images to get more detections. Default false +% - 'thresh': detection threshold. Default -Inf +% - 'multperim': allow multiple detections per detector per image. Default false. +% If any of these fields aren't specified, they will be read from ds.conf.params +% before using the defaults. +% +% Output is a set of detections in the standard format: one per row, +% each row is [x1 y1 x2 y2 score detector_id image_id flip boxid]. Note +% that in this distribution, flip and boxid are not read. +function [dets,feats]=detectInIm(model,imid,conf) + if(~exist('conf','var')) + conf=struct(); + end + if(~dsfield(conf,'thresh')) + conf.thresh=-Inf; + end + conf2=conf; + conf2.thresh=model.b+conf.thresh; + boxid=[]; + [pos,dist,clustid,feats,flip,boxid]=bestInImbb(model.w,imid,conf2); + dist=dist-model.b(clustid); + if(isempty(dist)) + dets=zeros(0,9); + else + dets=[pos.x1,pos.y1,pos.x2,pos.y2,dist,model.id(clustid),repmat(imid,numel(dist),1),flip,boxid]; + end +end diff --git a/dispClassifier.m b/dispClassifier.m new file mode 100755 index 0000000..d83d8eb --- /dev/null +++ b/dispClassifier.m @@ -0,0 +1,118 @@ +% Generate a heatmap display of the classifier, which shows the pixels in +% the image that were most influential. Basically finds which dimensions +% of the image descriptor were most influential and back-projects them to +% the max firing within each region of the spatial pyramid. Note that this +% tends to generate a sparse representation of textured regions; I could +% probably do better, but there was no time. +% +% detr: element detectors +% im: image +% svm: final SVM +% feattransf: a function which converts max-pooled responses into a feature +% vector suitable for svm classification. +% dets: topn detections for each detector in detr +% dispimset: index of the image set where dets came from (note: in general, +% im was loaded from the test images, dets are from training images. +% displayname: prefix for saving the displays +function [posheatmap, negheatmap] = dispClassifier(detr, im, svm, feattransf,dets,displayname,dispimset) +try + +global ds; + % first, re-generate the image descriptor. As we find detections, add them to + % the heat map. + pyramid = constructFeaturePyramid(im, ds.conf.params); + [features, levels, indexes,gradsums] = unentanglePyramid(pyramid, ... + ds.conf.params.patchCanonicalSize/ds.conf.params.sBins-2); + invalid=(gradsums<9); + size(features) + features(invalid,:)=[]; + levels(invalid)=[]; + indexes(invalid,:)=[]; + gradsums(invalid)=[]; + disp(['threw out ' num2str(sum(invalid)) ' patches']); + patsz=ds.conf.params.patchCanonicalSize; + fsz=(patsz-2*ds.conf.params.sBins)/ds.conf.params.sBins; + pos=pyridx2pos(indexes,reshape(levels,[],1),fsz,pyramid); + posy=(pos.y1 + pos.y2)/2+.000001; + posx=(pos.x1 + pos.x2)/2+.000001; + posheatmap=zeros(size(im(:,:,1))); + negheatmap=zeros(size(posheatmap)); + idx=1; + feature=zeros(5,size(detr.w,1)); + wmat=reshape(svm.w,5,[]); + for(i=[-1 1]) + for(j=[-1 1]); + posidx=find(i*(posy-size(im,1)/2) > 0 & j*(posx-size(im,2)/2) > 0); + [assignedidx,dist]=assigntoclosest(detr.w,features(i*(posy-size(im,1)/2) > 0 & j*(posx-size(im,2)/2) > 0,:),1); + dist=dist(:)-detr.b; + posidx2=posidx(assignedidx); + posmat=[pos.x1 pos.y1 pos.x2 pos.y2]; + posmat2=posmat(i*(posy-size(im,1)/2) > 0 & j*(posx-size(im,2)/2) > 0,:); + if(exist('feattransf','var')) + dist=feattransf(dist')'; + end + feature(idx,:)=dist(:)'; + assignedidxall{idx}=assignedidx; + posall{idx}=posmat2(assignedidx,:); + wt=dist(:)'.*wmat(idx,:); + posheatmap=posheatmap+genheatmap(wt(wt>0)',posmat2(assignedidx(wt>0),:),size(posheatmap)); + negheatmap=negheatmap+genheatmap(-wt(wt<0)',posmat2(assignedidx(wt<0),:),size(posheatmap)); + idx=idx+1; + end + end + [feature(end,:),maxpos]=max(feature(1:end-1,:),[],1); + wt=feature(end,:).*wmat(end,:); + for(i=1:4) + posheatmap=posheatmap+genheatmap(wt(wt>0&maxpos==i)',posall{i}(wt>0&maxpos==i,:),size(posheatmap)); + negheatmap=negheatmap+genheatmap(-wt(wt<0&maxpos==i)',posall{i}(wt<0&maxpos==i,:),size(posheatmap)); + end + hm=posheatmap-negheatmap-svm.rho/numel(posheatmap); + posheatmap=hm.*(hm>0); + negheatmap=-hm.*(hm<0); + % if we got detections as an argument, generate two displays to show the ones + % that contributed most to this detection: one for negative contributions, + % one for positive. + if(nargin>4) + currimset=ds.conf.currimset; + if(exist('dispimset','var')) + ds.conf.currimset=dispimset;%assume imgs is loaded; don't want to run dsup. + end + contrib=sum(feature.*wmat,1); + [wt,todisp]=maxk(contrib,25); + todisp(wt<0)=[]; + wt(wt<0)=[]; + todisp=detr.id(todisp); + dsup([displayname '_pos.patchimg'],extractpatches(dets(ismember(dets(:,6),todisp),:))); + conf=struct('dets',dets(ismember(dets(:,6),todisp),:),'detrord',todisp,... + 'message',{cellfun(@(x) ['contribution:' num2str(x)],num2cell(wt),'UniformOutput',false)}); + mhprender('patchdisplay.mhp',[displayname '_pos.displayhtml'],conf); + [wt,todisp]=mink(contrib,50); + todisp(wt>0)=[]; + wt(wt>0)=[]; + todisp=detr.id(todisp); + dsup([displayname '_neg.patchimg'],extractpatches(dets(ismember(dets(:,6),todisp),:))); + conf=struct('dets',dets(ismember(dets(:,6),todisp),:),'detrord',todisp,... + 'message',{cellfun(@(x) ['contribution:' num2str(x)],num2cell(wt),'UniformOutput',false)}); + mhprender('patchdisplay.mhp',[displayname '_neg.displayhtml'],conf); + if(exist('dispimset','var')) + ds.conf.currimset=currimset; + end + end + + feature=feature(:); + im=repmat(rgb2gray(im),[1,1,3]); + posheatmap=heatmap2jet(posheatmap*40000)*.5+im*.5; + negheatmap=heatmap2jet(negheatmap*40000)*.5+im*.5; +catch ex,dsprinterr;end +end + +function res=heatmap2jet(heatmap) + cmp=colormap('jet'); + res=zeros([size(heatmap) 3]); + heatmap=round(heatmap*size(cmp,1)); + heatmap(heatmap<1)=1; + heatmap(heatmap>size(cmp,1))=size(cmp,1); + for(chan=1:3) + res(:,:,chan)=reshape(cmp(heatmap,chan),size(heatmap)); + end +end diff --git a/display_indoor67.m b/display_indoor67.m new file mode 100644 index 0000000..17220a3 --- /dev/null +++ b/display_indoor67.m @@ -0,0 +1,29 @@ +ds_html{end+1}=sprintf('\n'); +ds_html{end+1}=sprintf(''); +for(i=1:67) +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' '); +for(j=1:10) +ds_html{end+1}=sprintf(''); +end +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' '); +for(j=11:20) +ds_html{end+1}=sprintf(''); +end +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf('\n'); +ds_html{end+1}=sprintf(''); +end +ds_reshtml=cell2mat(ds_html); +ds_html=[]; diff --git a/display_indoor67.mhp b/display_indoor67.mhp new file mode 100644 index 0000000..7a6cd82 --- /dev/null +++ b/display_indoor67.mhp @@ -0,0 +1,11 @@ +

    '); +ds_html{end+1}=num2str([argv.classnames{i}]); +ds_html{end+1}=sprintf('

    +<%for(i=1:67)%> + + + <%for(j=1:10)%><%end%> + + + <%for(j=11:20)%><%end%> + + +<%end%> diff --git a/distGenPooledFeats.m b/distGenPooledFeats.m new file mode 100755 index 0000000..9d940bb --- /dev/null +++ b/distGenPooledFeats.m @@ -0,0 +1,13 @@ +% given a set of element detectors, generate a feature vector +% for an image that's suitable for classification in an SVM. +% Just performs genPoolFeats2 on the both flips of the image +% and averages the resulting feature vectors. +function feats=distGenPoolFeats(detrs,imidx); + global ds; + i=imidx; + im=im2double(getimg(ds,i)); + feats=genPoolFeats2(detrs,im); + im=im(:,end:-1:1,:); + feats=(feats+genPoolFeats2(detrs,im))/2; + feats=feats(:)'; +end diff --git a/doGradDescentproj.m b/doGradDescentproj.m new file mode 100755 index 0000000..656c892 --- /dev/null +++ b/doGradDescentproj.m @@ -0,0 +1,388 @@ +% The core optimizer. It performs gradient ascent on +% sum_{i where Y(i)>0} max(w(:)'*X(:,i)*weights(i),0) + nrmlambda*norm(w(1:end-1)) ['the objective'] +% subject to sum_{i where Y(i)<=0} max(w(:)'*X(:,i)*weights(i),0)=beta. ['the constraint'] +% Note that this is not a convex optimization, and this function does not +% do anything special to correct for bad initial points. If you give it +% a random initialization, don't expect it to produce sane results! +function [ wOut outLabels miscstate ] = doGradDescentproj( X, Y, wIn ,weights,roundid) +global ds; +disp([num2str(size(X,2)) ' points, ' num2str(sum(Y>0)) ' pos']) +try +Y = Y(:); + +% read the parameters from dswork. +dsload('ds.round.lambda'); +if(dsfield(ds,'round','lambda')) + nrmlambda=ds.round.lambda; +elseif(dsfield(ds,'conf','params','lambda')) + nrmlambda=ds.conf.params.lambda; +else + nrmlambda=.002; +end +if(dsfield(ds,'conf','params','stepsize')) + stepsize=ds.conf.params.stepsize; +else + stepsize=.1; +end +if(dsfield(ds,'conf','params','optimizationComputeLimit')) + optimizationComputeLimit=ds.conf.params.optimizationComputeLimit; +end +dsload('ds.round.beta'); +if(dsfield(ds,'round','beta')) + beta=ds.round.beta; +elseif(dsfield(ds,'conf','params','beta')) + beta=ds.conf.params.beta; +else + beta=1; +end + +% In the current implementation, the feature vectors don't have the +% row of -1's appended +if (~all(X(end,:) == 1)) + fprintf('Appending row of -ones to feature vector\n'); + X = [X; -ones(1, size(X,2))]; +end + +if(sum(Y<=0)==0) + error('no negative points!'); +end + +% If this is one of the initialization rounds, we only update the bias term b. +% TODO: don't hardcode the 3. +if(roundid>-1 && roundid<=3) + wOut=wIn(:); + if(isempty(X)) + scores=[]; + return; + end + scores=(wOut(:)')*X; + negs=scores(Y<=0); + % fminsearch on the squared amount that the constraint is violated. However, + % fminsearch can miss the zero point if x gets too large and the gradient vanishes. + % Hence give a huge penalty for x in the region with zero gradient. + toadd=fminsearch(@(x) (sum(x+negs((x+negs)>0))-beta)^2 +1e10*(sum((x+negs)>0)==0),0,optimset('tolX',eps)) + wOut(end)=wOut(end)-toadd; + scores=scores+toadd; + toadd=.0001-min(max(scores(Y<=0)),max(scores(Y>0))); + toadd=max(0,toadd); + wOut(end)=wOut(end)-toadd; + outLabels=scores+toadd; + return; +end + +% If X has more rows than columns (more features per patch than patches) +% use a qr decomposition to rotate the space such that the last size(X,1)-size(X,2) +% dimensions are all zero. Then we can discard those dimensions. This can provide +% a big speed up for only marginal loss in numeric precision. +if(size(X,1)>size(X,2)) + X=X(1:end-1,:); + [B,~]=qr(X); + B=B(:,1:size(X,2)); + X=(B')*X; + X(end+1,:)=-1; + b=wIn(end); + wIn=((wIn(1:end-1)')*B)'; + wIn(end+1)=b; + ranqr=1; +else + ranqr=0; +end + +% If the weights (alphas) aren't set, then set them to all ones. +% Note that even in the version of the algorithm without cross-talk, we still +% use the weights to handle redundant detections in an image. +if(~exist('weights','var')) + weights=ones(size(Y)); + if(dsbool(ds.conf.params,'posweight')) + weights(Y==1)=ds.conf.params.posweight; + end +end +weights=weights(:)'; + +w = wIn; + +w_orig=w; + +% Keep track of the number of vector-matrix multiplies, approximately +% tracking the compute time. +nprods=0; +i=0; +% This should never be needed in the current implementation, but this counts +% the number of times we've 'fixed' a bad bias (see below). +nfails=0; +% The norm of the update to w. +nrmstep=Inf; +% The number of times we've taken a step without crossing any hinges (i.e. +% a point where w'*X(:,i) changes sign). +nquadmins=0; +while(nprods 1e-8 && nquadmins<10) + i=i+1; + wX = w'*X; + nprods=nprods+1; + + % If we received a bad initialization, it might be the case that none + % of the negative points have a score above zero, and so the gradient + % of the constraint will be zero and everything explodes. Hence we 'fix' + % the bias by increasing it until at least something is above zero. + while(sum(weights((wX)'>0 & Y<=0).*(wX((wX)'>0 & Y<=0)))==0) + disp('too few members, fixing bias...'); + nfails=nfails+1; + if(nfails>100000) + save([dsload('.ds.conf.dispoutpath') '/biasfail' num2str(floor(rand*1000))]); + error('too many bias fails'); + end + toadd=.01; + wX=wX+toadd; + w(end)=w(end)-toadd; + end + + % On the first round only, we use fminsearch to set the bias directly + % in a way that satisfies the constraint. + if(i==1) + negs=wX(Y<=0); + negweights=weights(Y<=0); + toadd=fminsearch(@(x) ((x+negs((x+negs)>0))*negweights((x+negs)>0)'-beta)^2 +1e10*(sum((x+negs)>0)==0),0,optimset('tolX',eps)) + w(end)=w(end)-toadd; + wX=wX+toadd; + end + + % compute the gradient of the objective sum_{i where Y(i)>0} max(w(:)'*X(:,i)*weights(i),0) + % which is just the weighted sum of the positive datapoints whose score is above zero. + lfull = wX.*weights; + lfull(wX<0)=0; + ldashfull = (wX>=0).*weights; + onSet = lfull' > 0; + contribIdx = Y>0; + lminus = sum(lfull((~contribIdx) & onSet)); + ldashplus = X(:,contribIdx& onSet) * ldashfull(contribIdx & onSet)'; + + % compute the gradient of norm(w) + ldashnrm=2*w*nrmlambda; + ldashnrm(end)=0; + + % project the gradient onto the tangent space of the constraint at the point w. + projspace=X(:,(~contribIdx) & onSet)*weights((~contribIdx) & onSet)'; + nprods=nprods+1; + projspace=projspace/norm(projspace); + nabla=ldashplus-ldashnrm; + nabla = nabla-projspace*(nabla'*projspace); + + % Take the affine approximation of the current constraint set--i.e. assume we can + % take as large a step as we want in the curent direction nabla without violating + % the constraint. It's a simple quadratic equation. Compute the best step we can take. + normquad=-[sum(nabla(1:end-1).^2),sum(2.*w(1:end-1).*nabla(1:end-1)),sum(w(1:end-1).^2)]*nrmlambda; + ldashplusquad=ldashplus'*nabla; + quadmin=(normquad(2)+ldashplusquad)/(-2*normquad(1)); + % numerical inaccuracy may cause quadmin to be slightly negative, in which case we + % should simply correct it to zero. If it's too negative (<-1e-8), something is broken + % in the optimizer. + if(quadmin<-1e-8) + error('quadmin was negative!'); + end + if(quadmin<0) + quadmin=0; + end + if(isnan(quadmin)) + error('quadmin was nan'); + end + % TODO: get rid of this section + % next, compute how big a step we can take without causing the sign of the score any patch + % to change. + % dall contains the gradient of the score of each patch with respect to the step size + %dall=nabla'*X; + % now dall contains the step which causes a sign change for a particular point + %dall=-wX./dall; + % any negative points are actually getting farther from the hinge as we take a larger + % step; ignore them. + %dall(dall<0)=Inf; + % now get the smallest step. + %[dall,dallidx]=sort(dall,'ascend'); + + % The alternative is a fixed step size. In practice the optimization is + % slightly faster if the step size is smaller when the weights are + % larger, but altering the fixed step size using this heuristic + % probably destroys all convergence guarantees. Note the optimization + % still works without this heuristic. + step=stepsize/sum(weights(contribIdx&onSet)); + wold=w; + + if(step100000) + save([dsload('.ds.conf.dispoutpath') '/biasfail' num2str(floor(rand*1000))]); + error('too many bias fails'); + end + toadd=.01; + negwX=negwX+toadd; + w(end)=w(end)-toadd; + end + + % Now that we have taken a step with respect to the objective function, + % the constraint is probably violated. Hence, we perform gradient descent + % on f(w)=|sum_{i where Y(i)<=0} max(w(:)'*X(:,i)*weights(i),0)-beta| + negweight=weights(~contribIdx); + totneg=negWx(negWx>0)*negweight(negWx>0)'; + neggt0=~contribIdx; + + % We need to do careful bookkeeping on the set of patches whose score + % is greater than zero--i.e. those patches that contribute to the gradient + % of f. The gradient is constant except when we hit a hinge, at which + % point a patch will either begin or stop contributing to the gradient + % of f. Hence, whenever we take a step, we step all the way to one + % of these hinges. However, once we've taken the step, we can't just + % test whether w'*X(:,i)>0 for all i, because one patch will + % score close to 0, but not exactly zero due to precision issues. + % We hence need to *force* that particular patch to either start or stop + % contributing to the gradient of f. neggt0 does that bookkeeping. + % + % I am actually not entirely sure that this does the bookkeeping correctly-- + % i.e. I think that if two patches have their scores hit 0 at the same time, + % one of them might not have their corresponding entry in neggt0 flipped + % properly. In practice, however, I have not noticed this becoming a problem, + % and any errors will get corrected on the next iteration anyway when neggt0 + % gets recomputed from scratch. + neggt0(~contribIdx)=negWx>0; + nprojs=0; + while(1) + % negWx contains the score of each negative point, and projdir contains + % the gradient of f. Note, however, that both of these quantities + % can be computed incrementally, since we know the gradient of the + % score of each point at every step of the optimization, and we know that + % this gradient stays constant, and we know the contribution of each point + % to the gradient. Error accumulates, however, so we + % re-compute negWx every 20 iterations. The constant 20 has not been tuned. + if(mod(nprojs,20)==0) + projdir=X(:,neggt0)*weights(neggt0)'; + nprods=nprods+1; + if(nprojs>0) + negWx=w'*X(:,(~contribIdx)); + nprods=nprods+1; + end + end + % compute the gradient of the score of each of the positives with respect to the + % current projection direction. + % Note that this could be computed incrementally if we pre-computed the kernel + % matrix X'*X, and this would probably lead to a big speedup. Haven't tried it + % though. + dnegs=projdir'*X(:,(~contribIdx)); + nprods=nprods+1; + % compute how far we have to step before the constraint is satisfied, assuming + % we don't hit any hinges. + zer=(beta-totneg)./(dnegs(neggt0(~contribIdx))*weights(neggt0)'); + % If it turns out we're stepping in the wrong direction, step in the right direction. + % Necessary because of the absolute value. + if(zer<0) + projdir=-projdir; + zer=-zer; + dnegs=-dnegs; + end + + % Figure out how far we can step before each of the datapoints hits their hinge. + signchg=-negWx./dnegs; + % If the sign change is negative, it means the current gradient direction is going + % away from that point's hinge. + signchg(signchg<=1e-8)=Inf; + % If we hit a hinge before we satisfy the constraint, step to that hinge. Otherwise, + % step to the point which satisfies the constraint. + if(min(signchg)1e-10) + nprojs=nprojs+1; + [msc,idx]=min(signchg); + fContribIdx=find(~contribIdx); + % wXidx is the index (i.e. column of X) of the point that hit its hinge + wXidx=fContribIdx(idx); + w=w+projdir*msc; + negWx=negWx+dnegs*msc; + newwx=zeros(size(wX)); + newwx(~contribIdx)=negWx; + if(neggt0(wXidx)) + projdir=projdir-X(:,wXidx)*weights(wXidx); + else + projdir=projdir+X(:,wXidx)*weights(wXidx); + end + neggt0(wXidx)=~neggt0(wXidx); + totneg=newwx(neggt0)*weights(neggt0)'; + else + w=w+projdir*zer; + break; + end + end + else + w=w+nabla*quadmin; + nrmstep=norm(nabla*quadmin); + disp('quadminned'); + end + nrmw=(norm(w(1:end-1))); + + % In general, we're running this optimization on a tiny subset of the total + % patch space. Hence, if w changes too much, we're computing ratios in a + % part of patch space where we have few or no samples, and so our estimate + % of the gradient is going to be bad. This is an ugly hack to terminate + % if it seems like we've drifted too far, which we ran out of space + % in the paper to explain. Let p be a point on the intersection of the decision + % boundary defined by w_orig and the unit hypersphere. Let theta be the angle + % between w_orig(1:end-1) and p. Let Q be the set of all points q on the unit + % hypershpere such that the angle between w_orig(1:end-1) and q is theta/2. + % Make sure that Q lies entirely on the positive side of the hyperplane + % defined by w. Perform the same test with the roles of w and w_orig reversed. + % If both tests pass, we keep going; otherwise we return for more patches. + anglechange=getangle(w(end),w(1:end-1))*.5>getangle(w_orig(end),w_orig(1:end-1))-acos(dot(w_orig(1:end-1),w(1:end-1))/(norm(w(1:end-1))*norm(w_orig(1:end-1)))); + anglechange=anglechange || getangle(w_orig(end),w_orig(1:end-1))*.5>getangle(w(end),w(1:end-1))-acos(dot(w_orig(1:end-1),w(1:end-1))/(norm(w(1:end-1))*norm(w_orig(1:end-1)))); + angorig=getangle(w_orig(end),w_orig(1:end-1)); + angnew=getangle(w(end),w(1:end-1)); + angdiffval=acos(dot(w_orig(1:end-1),w(1:end-1))/(norm(w(1:end-1))*norm(w_orig(1:end-1)))); + if(anglechange&&~dsbool(ds.conf.params,'nocheck')) + w=wold; + if(anglechange) + disp('optimization wants to leave safe zone. returning to get more patches.'); + break; + end + end + % print out a bunch of debug info. + if(mod(i,10)==0||nprods>=optimizationComputeLimit) + disp(['done: ' num2str(i) ' ' num2str(toc) ' seconds']); + disp(['lminus:' num2str(lminus)]); + disp(['w_end:' num2str(w(end))]); + disp(['ang_orig:' num2str(angorig)]); + disp(['ang_new:' num2str(angnew)]); + disp(['ang_dot:' num2str(angdiffval)]); + disp(['nabla_end:' num2str(nabla(end))]); + disp(['nrmstep:' num2str(nrmstep)]); + disp(['step:' num2str(step)]); + disp(['quadmin:' num2str(quadmin)]); + disp(['norm w:' num2str(nrmw)]); + disp(['active wX (pos neg): (' num2str(sum(wX(:)>0 & Y(:)>0)) ' ' num2str(sum(wX(:)>0 & Y(:)<=0)) ')']); + end +end +disp(['made it through ' num2str(i) ' rounds']); +wX = w'*X; +outLabels=wX; +if(sum(outLabels>0)<2) + error('too few patches in cluster...'); +end + +wOut = w; + +if(ranqr); + b=wOut(end); + wOut(end)=[]; + wOut=B*wOut; + wOut(end+1)=b; +end +wOut(end)=(wOut(end)); +catch ex;dsprinterr;end +end + +function res=getangle(bias,vec) + res=acos(-bias/norm(vec)); +end diff --git a/errdisp.m b/errdisp.m new file mode 100755 index 0000000..af13e3a --- /dev/null +++ b/errdisp.m @@ -0,0 +1,79 @@ +ds_html{end+1}=sprintf('\n'); +ds_html{end+1}=sprintf('

    <%=argv.classnames{i}%>

    \n'); +ds_html{end+1}=sprintf(' '); +for(i=1:numel(ds.todisp)) +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' '); +end +ds_html{end+1}=sprintf('

    '); +if(~argv.iserror) +ds_html{end+1}=sprintf('top guess'); +else +ds_html{end+1}=sprintf('ground truth'); +end +ds_html{end+1}=sprintf(' (pos) (confindence '); +ds_html{end+1}=num2str([argv.confidence(i)]); +ds_html{end+1}=sprintf('):'); +ds_html{end+1}=num2str([argv.trueclasses{i}]); +ds_html{end+1}=sprintf('

    '); +if(~argv.iserror) +ds_html{end+1}=sprintf('top guess'); +else +ds_html{end+1}=sprintf('ground truth'); +end +ds_html{end+1}=sprintf(' (neg):'); +ds_html{end+1}=num2str([argv.trueclasses{i}]); +ds_html{end+1}=sprintf('

    '); +if(~argv.iserror) +ds_html{end+1}=sprintf('second guess'); +else +ds_html{end+1}=sprintf('guess'); +end +ds_html{end+1}=sprintf(' (pos):'); +ds_html{end+1}=num2str([argv.guessclasses{i}]); +ds_html{end+1}=sprintf('

    '); +if(~argv.iserror) +ds_html{end+1}=sprintf('second guess'); +else +ds_html{end+1}=sprintf('guess'); +end +ds_html{end+1}=sprintf(' (neg):'); +ds_html{end+1}=num2str([argv.guessclasses{i}]); +ds_html{end+1}=sprintf('
    \n'); +ds_html{end+1}=sprintf('\n'); +ds_reshtml=cell2mat(ds_html); +ds_html=[]; diff --git a/errdisp.mhp b/errdisp.mhp new file mode 100755 index 0000000..5fcb8a3 --- /dev/null +++ b/errdisp.mhp @@ -0,0 +1,13 @@ + + + <%for(i=1:numel(ds.todisp)) %> + + + + + + + + <%end %> +

    <%if(~argv.iserror)%>top guess<%else%>ground truth<%end%> (pos) (confindence <%=argv.confidence(i)%>):<%=argv.trueclasses{i}%>

    <%if(~argv.iserror)%>top guess<%else%>ground truth<%end%> (neg):<%=argv.trueclasses{i}%>

    <%if(~argv.iserror)%>second guess<%else%>guess<%end%> (pos):<%=argv.guessclasses{i}%>

    <%if(~argv.iserror)%>second guess<%else%>guess<%end%> (neg):<%=argv.guessclasses{i}%>
    + diff --git a/extractpatches.m b/extractpatches.m new file mode 100755 index 0000000..bea80df --- /dev/null +++ b/extractpatches.m @@ -0,0 +1,104 @@ +% Author: Carl Doersch (cdoersch at cs dot cmu dot edu) +% +% Extract patches corresponding to detections in images. +% detsimple is the set of detections, with a 'pos' field +% specifying positions, and a 'imidx' field, specifying +% an index into the current image set. imgs_all us unused. +function res=extractpatches(detsimple,imgs_all,conf) +global ds; +if(~isstruct(detsimple)) + detsimple=mat2det(detsimple); +end +if(~exist('conf','var')) + conf=struct(); +end +try +% dsload('ds.imgs'); +% global imgs; +% if(isempty(imgs)) + tmpimgs=dsload('.ds.imgs{ds.conf.currimset}'); + imgs=cell(numel(tmpimgs.fullname),1); +% end +numel(detsimple) +loaded=[]; +ct=1; +if(~dsbool(conf,'featurize')) + res=cell(1,numel(detsimple)); +end +[~,ord]=sort([detsimple.imidx]); + for(dsidx=ord(:)') + pos=detsimple(dsidx).pos; + i=detsimple(dsidx).imidx; + if(i==0) + if(isfield(conf,'explicitpatch')&&~isempty(conf.explicitpatch{dsidx})) + res{dsidx}=conf.explicitpatch{dsidx}; + else + res{dsidx}=cat(3,1,1,1); + end + if(~dsbool(conf,'noresize')) + pat=imresize(res{dsidx},[reszy reszx]); + end + continue; + end + if(numel(imgs)0||pady>0) + imgs{i}=padarray(imgs{i},[pady padx 0],'replicate'); + end + loaded(loaded==i)=[]; + loaded=[loaded i]; + if(numel(loaded)>1) + imgs{loaded(1)}=[]; + loaded(1)=[]; + end + end + %if(padsize>0) + %toaddy=round((pos.y2-pos.y1+1)*padsize); + %toaddx=round((pos.x2-pos.x1+1)*padsize); + pos.y1=pos.y1+pady; + pos.y2=pos.y2+pady; + pos.x1=pos.x1+padx; + pos.x2=pos.x2+padx; + %end + if(dsbool(conf,'noresize')) + pat=imgs{i}(pos.y1:pos.y2,pos.x1:pos.x2,:); + else + maxsz=max(pos.y2-pos.y1,pos.x2-pos.x1); + if(dsbool(conf,'explicitsize')) + reszx=conf.explicitsize(2); + reszy=conf.explicitsize(1); + else + reszx=80;%*(pos.x2-pos.x1)/maxsz; + reszy=80;%*(pos.y2-pos.y1)/maxsz; + end + pat=imresize(imgs{i}(pos.y1:pos.y2,pos.x1:pos.x2,:),[reszy reszx]); + end + if(dsbool(detsimple(dsidx),'flip')) + pat=pat(:,end:-1:1,:); + end + if(dsbool(conf,'featurize')) + conf.imid=i; + feat=patch2feat({pat},conf); + if(~exist('res','var')) + res=zeros(numel(detsimple),numel(feat),'single'); + res(dsidx,:)=single(feat); + else + res(dsidx,:)=single(feat); + end + else + res{dsidx}=pat; + end + if(mod(ct,10)==0) + disp(ct) + end + ct=ct+1; + + end + dsclear('.ds.overalldets'); +catch ex + dsprinterr +end diff --git a/findOverlapping3.m b/findOverlapping3.m new file mode 100755 index 0000000..a2ebd3e --- /dev/null +++ b/findOverlapping3.m @@ -0,0 +1,211 @@ +% datasource: a matrix of detections, or a path in dswork containing +% detections, in which case the detections will be read one column at +% a time, and it is assumed that all detections for one detector will +% be contained in a single cell of datasource. in this case, inds will +% contain the indices to read. +% classfordetr: a two-column matrix where the first column is a detector id, +% and the second is the class that the detector was sampled from. +% conf can contain: +% 'overlapthresh': the threshold for two patches in the same image to be considered +% overlapping +% 'ndetsforoverlap': maximum number of detections in common before two detectors are +% clustered together +% 'clusterer': 'greedy' implies the greedy de-duplication procedure described on +% the SIGGRAPH '12 paper. 'agglomerative' is the agglomerative clustering +% algorithm used to cluster patches in the NIPS '13 paper. +%detids: ids of nonoverlapping detectors, sorted by score +%scores: scores of detectors in detids, synchronized with detids +%component: the component of overlapping detectors; first column is all detids sorted by scores, +% second is the root detid that a given detector overlaps with, +% third is score corresponding to detector +function [detids,scores,component]=findOverlapping3(datasource,inds,classfordetr,conf) + if(~exist('conf','var')) + conf=struct(); + end + if(~dsfield(conf,'overlapthresh')) + conf.overlapthresh=.3; + end + if(~dsfield(conf,'maxoverlaps')) + conf.maxoverlaps=5; + end + if(~dsfield(conf,'ndetsforoverlap')) + conf.ndetsforoverlap=Inf; + end + if(~dsfield(conf,'clusterer')) + conf.clusterer='greedy'; + end + imgs=dsload('.ds.imgs{ds.conf.currimset}'); + try + global ds; + alldata={}; + scores=[]; + detids=[]; + if(ischar(datasource)) % Always false in current implementation + disp('loading...') + for(i=inds(:)') + dat=structcell2mat(dsload([datasource '{1:' num2str(dssavestatesize(datasource,1)) '}{' num2str(i) '}'],'clear')); + if(isstruct(dat)) + dat=det2mat(dat); + end + if(isempty(dat)) + dat=zeros(0,7); + end + [~,posinclassfordetr]=ismember(dat(:,6),classfordetr(:,1)); + positivedets=dat(classfordetr(posinclassfordetr,2)==imgs.label(dat(:,7)),:); + positivedets=distributeby(positivedets,positivedets(:,6)); + for(k=1:numel(positivedets)) + ndfo=conf.ndetsforoverlap; + if(ndfo>0&ndfo<1) + ndfo=round(sum(positivedets{k}(:,5)>0)*ndfo); + end + [~,tmpinds]=maxk(positivedets{k}(:,5),ndfo); + alldata{end+1,1}=positivedets{k}(tmpinds,[1:4,6:7]); + end + clear positivedets; + mydetids=unique(dat(:,6)); + for(j=1:numel(mydetids)) + if(~isfield(conf,'sortscores')) + %if(sortcount) + %ispos=classfordetr(posinclassfordetr,2)==imgs.label(dat(:,7)); + %ip=ispos(dat(dat(:,6)==mydetids(j),7)); + %scrtmp=dat(dat(:,6)==mydetids(j),5); + %[~,ord]=sort(scrtmp,'descend'); + %if(dsfield(conf,'sortn')) + % sortn=conf.sortn; + %else + % sortn=30; + %end + scores(end+1)=0;%sum(ip(ord(1:conf.sortn))); + %else + % scores(end+1)=scoreFromDots(dat(dat(:,6)==mydetids(j),5),ispos(dat(dat(:,6)==mydetids(j),7)+1),ds.conf.params); + %end + end + detids(end+1)=mydetids(j); + end + disp(i); + end + if(dsfield(conf,'sortscores')) + [~,ord]=ismember(detids,conf.sortscores(:,1)); + scores=conf.sortscores(ord,2); + end + alldata=cell2mat(alldata); + else + if(~(dsfield(conf,'sortn') || dsfield(conf,'sortscores')))% always false in current implementation + detids=sort(unique(datasource(:,6))); + for(i=numel(detids):-1:1) + inds=find(datasource(:,6)==detids(i)); + [~,~,counter]=unique(imgs.city(datasource(inds,7))); + [scores(i),maxid]=max(histc(counter,1:max(counter))); + scores(i)=scores(i)/numel(counter); + alldata{i}=datasource(inds(counter==maxid),[1:4,6:7]); + end + else + [datasource,detids]=distributeby(datasource,datasource(:,6)); + if(dsfield(conf,'sortscores')) % always false in current implementation + [~,ord]=ismember(detids,conf.sortscores(:,1)); + scores=conf.sortscores(ord,2); + for(i=numel(datasource):-1:1) + [~,tmpinds]=maxk(datasource{i}(:,5),conf.ndetsforoverlap); + alldata{i}=datasource{i}(tmpinds,[1:4,6:7]); + end + else + for(i=numel(datasource):-1:1) + % select the top detections for each detector. Get rid of the 'score' column + % for each patch because I wrote this before I'd standardized how I stored the detections. + [~,posinclassfordetr]=ismember(datasource{i}(:,6),classfordetr(:,1)); + isdetpos=classfordetr(posinclassfordetr(:,2))'==imgs.label(datasource{i}(:,7)); + [~,detord]=maxk(datasource{i}(:,5),conf.sortn); + scores(i)=sum(isdetpos(detord))/conf.sortn; + posdets=datasource{i}(isdetpos,:); + [~,tmpinds]=maxk(posdets(:,5),conf.ndetsforoverlap); + alldata{i}=posdets(tmpinds,[1:4,6:7]); + end + end + end + %alldata=datasource(:,[1:4,6:7]); + alldata=cell2mat(alldata'); + clear datasource; + end + disp('sort by image'); + [~,ord]=sort(scores,'descend'); + [~,alldata(:,5)]=ismember(alldata(:,5),detids(ord));%relabel detector id's such that highest scoring detectors get lowest id's + [~,imgord]=sort(alldata(:,6)); + alldata=alldata(imgord,:); + alldata=mat2cell(alldata,diff([0;find(diff(alldata(:,6))); size(alldata,1)]),size(alldata,2));%break up alldata into a cell array by image + disp('compute overlaps'); + overlapping={}; + % compute the overlapping patches for each image. + for(i=1:numel(alldata)) + overl = computeOverlap(alldata{i}(:,1:4), alldata{i}(:,1:4), 'pascal'); + overl(1:(size(overl,1)+1):end)=0; + [r,col]=find(overl>conf.overlapthresh); + ovldets=[alldata{i}(r,5) alldata{i}(col,5)]; + ovldets=ovldets(ovldets(:,1)conf.maxoverlaps),:); + links=sparse(links(:,2),links(:,1),ones(size(links(:,1))),numel(detids),numel(detids)); + hasoverlap=zeros(numel(detids),1); + hasoverlap(unique(overlapping(ends(hists>10),2)))=1; + old=ones(size(hasoverlap(:))); + while(~all(hasoverlap==old)) + old=hasoverlap; + hasoverlap=links*(~hasoverlap)>0; + end + + eliminate=find(hasoverlap); + + if(nargout>=3) + component=zeros(numel(hasoverlap),2); + component(:,1)=detids(ord); + for(i=1:size(component,1)) + if(hasoverlap(i)) + mylinks=links(i,(1:(i-1))).*(~hasoverlap(1:(i-1)))'; + mylinks=find(mylinks); + component(i,2)=component(mylinks(1),2); + else + component(i,2)=component(i,1); + end + end + end + elseif(strcmp(conf.clusterer,'agglomerative')) + % compute the 'distance' between two elements as the negative number of + % overlapping patches across all images plus a constant. + links=overlapping(ends(hists>0),:); + links=sparse(links(:,2),links(:,1),hists(hists>0),numel(detids),numel(detids)); + links=max(links,links'); + minval=max(links(:)); + % linkage2 is a copy of matlab's linkage function. linkage has a bug + % a bug that makes it crash on the sorts of inputs I give it. + cluststr=linkage2((1:numel(detids))','average',{@(x,y) -links(x,y)+minval}); + component=cluster(cluststr,'cutoff',minval-conf.maxoverlaps+.001,'criterion','distance'); + component2=component; + %detrrank(ord)=1:numel(ord); + for(j=unique(component(:)')) + mindetrrank=min(find(component==j)); + component2(component==j)=detids(ord(mindetrrank)); + end + component=[c(detids(ord)) component2(:)]; + eliminate=component(:,1)~=component(:,2); + else + error('unrecognized clusterer'); + end + scores=scores(:); + if(nargout>=3) + component=[component scores(ord)]; + end + detids=detids(ord);%(ord(eliminate))=[]; + detids(eliminate)=[]; + scores=scores(ord);%(ord(eliminate))=[]; + scores(eliminate)=[]; + catch ex,dsprinterr;end +end diff --git a/findmatches.m b/findmatches.m new file mode 100755 index 0000000..e8bdd80 --- /dev/null +++ b/findmatches.m @@ -0,0 +1,30 @@ +%generally, targets are the detectors and toassign is patches from current image +function [assignedidx, dist, clustid]=findmatches(targets,toassign,thresh,conf); + global ds; + thresh=thresh(:); + if(~exist('conf','var')) + conf=struct(); + end + if(dsfield(conf,'rho')) + pertargrho=conf.rho(:); + else + pertargrho=0; + end + closest=zeros(size(toassign,1),1); + outdist=zeros(size(toassign,1),1); + assignedidx={};clustid={};dist={}; + for(i=1:800:size(toassign,1)) + inds=i:min(i+800-1,size(toassign,1)); + batch=toassign(inds,:); + inprod=targets*(batch'); + inprod=bsxfun(@plus,inprod,pertargrho); + [x,y]=find(bsxfun(@gt,inprod,thresh)); + clustid{end+1}=reshape(x,[],1); + assignedidx{end+1}=reshape(inds(y),[],1); + dist{end+1}=reshape(inprod(sub2ind(size(inprod),x(:),y(:))),[],1); + %[outdist(inds),closest(inds)]=max(dist,[],1); + end + assignedidx=structcell2mat(assignedidx'); + clustid=structcell2mat(clustid'); + dist=structcell2mat(dist'); +end diff --git a/genPoolFeats2.m b/genPoolFeats2.m new file mode 100755 index 0000000..22e0c94 --- /dev/null +++ b/genPoolFeats2.m @@ -0,0 +1,41 @@ +% Given a set of element detectors and an image, generate +% a feature vector suitable for use in an SVM. Specifically, +% run the detectors on the image, and find the max detection +% in each cell of a 2-level spatial pyramid (2x2 and 1x1). Hence +% the final representation has length numel(detr.id)*5. All features +% for one detector are stored consecutively. +function [feature] = genPoolFeats(detr, im) +global ds; + pyramid = constructFeaturePyramid(im, ds.conf.params); + [features, levels, indexes,gradsums] = unentanglePyramid(pyramid, ... + ds.conf.params.patchCanonicalSize/ds.conf.params.sBins-2); + invalid=(gradsums<9); + size(features) + features(invalid,:)=[]; + levels(invalid)=[]; + indexes(invalid,:)=[]; + gradsums(invalid)=[]; + disp(['threw out ' num2str(sum(invalid)) ' patches']); + patsz=ds.conf.params.patchCanonicalSize; + fsz=(patsz-2*ds.conf.params.sBins)/ds.conf.params.sBins; + pos=pyridx2pos(indexes,reshape(levels,[],1),fsz,pyramid); + posy=(pos.y1 + pos.y2)/2+.000001; + posx=(pos.x1 + pos.x2)/2+.000001; + idx=1; + feature=zeros(5,size(detr.w,1)); + % find the maximum match in each of the 4 grid cells separately + % (note assigntoclosest finds the best score in a set of feature vectors + % for each detector). + for(i=[-1 1]) + for(j=[-1 1]); + [~,dist]=assigntoclosest(detr.w,features(i*(posy-size(im,1)/2) > 0 & j*(posx-size(im,2)/2) > 0,:),1); + dist=dist(:)-detr.b; + feature(idx,:)=dist(:)'; + idx=idx+1; + end + end + % compute the 1x1 layer of the spatial pyramid as the max + % of the 2x2 layer. + feature(end,:)=max(feature(1:end-1,:),[],1); + feature=feature(:)'; +end diff --git a/genheatmap.m b/genheatmap.m new file mode 100755 index 0000000..ff80253 --- /dev/null +++ b/genheatmap.m @@ -0,0 +1,7 @@ +function res=genheatmap(wt,pos,sz) + res=zeros(sz(1),sz(2)); + pos=round(pos); + for(i=1:numel(wt)) + res(pos(i,2):pos(i,4),pos(i,1):pos(i,3))=res(pos(i,2):pos(i,4),pos(i,1):pos(i,3))+wt(i)/((pos(i,3)-pos(i,1)+1)*(pos(i,4)-pos(i,2)+1)); + end +end diff --git a/getFeaturesForLevel.m b/getFeaturesForLevel.m new file mode 100755 index 0000000..6e88749 --- /dev/null +++ b/getFeaturesForLevel.m @@ -0,0 +1,59 @@ +function [features, indexes, gradsums] = getFeaturesForLevel(level, params,prSize, pcSize,pzSize,nExtra,gradimg) +% Author: saurabh.me@gmail.com (Saurabh Singh) +%edited by carl +[rows, cols, dims] = size(level); +%level=cat(3,level,imresize(im(:,:,2),[rows cols],'bilinear'),imresize(im(:,:,3),[rows cols],'bilinear')); +%dims=dims+2; +rLim = rows - prSize + 1; +cLim = cols - pcSize + 1; +%keyboard; +featDim = prSize * pcSize * pzSize + nExtra +if(dsbool(params,'appendbias')) + features = ones(rLim * cLim, featDim+1); +else + features = ones(rLim * cLim, featDim); +end +if(exist('gradimg','var')) + gradsums=zeros(rLim * cLim,1); + creategrad=true; +else + creategrad=false; +end +%if(dsfield(params,'useColorHists')) +% useColorHists=true; +%else +% useColorHists=false; +%end +indexes = zeros(rLim * cLim, 2); +featInd = 0; +size(level) +for j = 1 : cLim + for i = 1 : rLim + feat = level(i:i+prSize-1, j:j+pcSize-1, :); +% featlab = im2(i:i+prSize-1, j:j+pcSize-1, :)*.03; + featInd = featInd + 1; + %if(useColorHists) + % endhist=sum(sum(feat(:,:,32:end))); + % features(featInd,:)=[reshape(feat(:,:,1:31),1,[]) endhist(:)']; + %else + feat=reshape(feat, 1, []); + %if(dsbool(params,'normalizefeats')) + % feat=(feat-mean(feat)); + % feat=feat./max(.0000001,norm(feat)); + %end + features(featInd, 1:size(feat,2)) = feat; + + %end + if(creategrad) + gradsums(featInd)=mean(mean(gradimg(i:i+prSize-1, j:j+pcSize-1))); + end + +% features(featInd, :) = reshape(feat, 1, featDim); + indexes(featInd, :) = [i, j]; + + end +end +%if(dsbool(params,'normalizefeats')) +% features=normrows(features,1,.000001); +%end +end diff --git a/getLevelScales.m b/getLevelScales.m new file mode 100755 index 0000000..c519b35 --- /dev/null +++ b/getLevelScales.m @@ -0,0 +1,5 @@ +function scales = getLevelScales(numLevels, interval) +% Author: saurabh.me@gmail.com (Saurabh Singh). +sc = 2^(1 / interval); +scales = sc.^(0 : numLevels - 1); +end diff --git a/getMinimalModel2.m b/getMinimalModel2.m new file mode 100755 index 0000000..6098d77 --- /dev/null +++ b/getMinimalModel2.m @@ -0,0 +1,23 @@ +function minModel = getMinimalModel(model,suppVec) +% precomputes the weight vectors. +if(~exist('suppVec','var')) + suppVec = model.SVs; +else + suppVec=suppVec(model.SVs,:); +end +coeff = model.sv_coef; +coeff = repmat(coeff, 1, size(suppVec, 2)); +minModel.rho = model.rho; +size(coeff) +size(suppVec) +minModel.w = sum(coeff .* suppVec); +firstLabel = model.Label(1); +minModel.w=minModel.w*firstLabel; +minModel.rho=minModel.rho*firstLabel; +if isfield(model, 'threshold') + minModel.threshold = model.threshold; +end +if isfield(model, 'info') + minModel.info = model.info; +end +end diff --git a/getRandForPdf.m b/getRandForPdf.m new file mode 100755 index 0000000..0b5f7d1 --- /dev/null +++ b/getRandForPdf.m @@ -0,0 +1,18 @@ +function numbers = getRandForPdf(dist, n) +cumul = cumsum(dist); +steps = 0:1/(length(dist)-1):1; +cumulInv = zeros(1, length(cumul)); +cumulInd = 1; +for i = 1 : length(steps) + if steps(i) < cumul(cumulInd) + cumulInv(i) = cumulInd; + else + while cumulInd < length(steps) && steps(i) > cumul(cumulInd) + eps + cumulInd = cumulInd + 1; + end + cumulInv(i) = cumulInd; + end +end +numbers = round(rand(1, n) * (length(dist) - 1)) + 1; +numbers = cumulInv(numbers); +end \ No newline at end of file diff --git a/getimg.m b/getimg.m new file mode 100755 index 0000000..6e5f0f6 --- /dev/null +++ b/getimg.m @@ -0,0 +1,41 @@ +% Author: Carl Doersch (cdoersch at cs dot cmu dot edu) +% +% Load an image at the position specified by idx. Optionally, +% the first parameter can be the ds structure, avoiding +% a call to global. +function res=getimg(ds,idx) + if(nargin<2) + idx=ds; + clear ds; + global ds; + end + imgs=dsload('.ds.imgs{ds.conf.currimset}'); + if(ds.conf.currimset==10&&idx==15842) + error('bad image!!'); + end + if(numel(imgs.fullname)ds.conf.params.maxpixels) + ressz=round([size(res,1),size(res,2)]*sqrt(ds.conf.params.maxpixels/(size(res,1)*size(res,2)))) + res=imresize(res,ressz); + end + end + if(dsfield(ds,'conf','params','minpixels')) + if(size(res,1)*size(res,2).5). Hence it's a 'softened' +% version of the greedy deduplication used in the SIGGRAPH 12 paper. +% This is the ranking used for the indoor67 experiments, and to be +% honest it doesn't work very well. +function [res,coverageinc]=greedySelectDetrsCoverage(indata,ispos,thresh,ntosel,conf) +try + if(~iscell(indata)) + indata={indata}; + end + if(~exist('conf','var')) + conf=struct(); + end + % first select the detections above the given level of purity. + % Do it separately for each cell of indata. + for(m=1:numel(indata)) + [data,elid]=distributeby(indata{m},indata{m}(:,6)); + ispos=ispos(:)'>0; + for(i=1:numel(data)) + dat=data{i}; + [~,ord]=sort(dat(:,5),'descend'); + dat=dat(ord,:); + isposdat=ispos(dat(:,7)); + % compute purity at each point in the ranking. + purity=cumsum(isposdat)./(1:numel(isposdat)); + tokeep=max(find(purity>=thresh)); + if(purity(end)>thresh) + disp(['warning: min purity for ' num2str(elid(i)) ' was ' num2str(purity(end))]); + end + if(~dsbool(conf,'useoverlap')) + data{i}=dat(1:tokeep,[1:4 6:7]); + imind=6; + else + data{i}=dat(1:tokeep,:); + imind=7; + end + data{i}=data{i}(ispos(data{i}(:,imind)),:); + [~,ord]=sort(data{i}(:,imind)); + data{i}=data{i}(ord,:); + if(mod(i,100)==0) + disp(i); + end + if(dsbool(conf,'legaldetrs')) + data{i}(~ismember(data{i}(:,imind-1),conf.legaldetrs),:)=[]; + end + end + data=cell2mat(data); + indata{m}=data; + end + if(~dsbool(conf,'useoverlap')) + % compute based on pixels + indata=cell2mat(indata(:)); + [~,indata(:,imind)]=ismember(indata(:,imind),unique(indata(:,imind))); + [res,coverageinc]=greedySelectDetrsCoveragemex(int64(indata),int64(ntosel)); + else + % compute based on detection counts. + indata=cell2mat(indata(:)); + maxdetr=max(indata(:,6)); + indata=distributeby(indata,indata(:,7)); + selected=[]; + % For each image, compute a matrix specifying which bounding boxes overlap with + % which other bounding boxes. + for(i=1:numel(indata)) + indata{i}=indata{i}(myNmsClass(indata{i},.5),:); + ovlp{i}=computeOverlap(indata{i}(:,1:4),indata{i}(:,1:4),'pedro')>.5; + end + for(j=1:ntosel) + % cts accumulates, for each detector, the total number of detections by that + % detector, weighted by the number of overlaps. + cts=zeros(maxdetr,1); + for(i=1:numel(ovlp)) + % for each detection d in a given image, sovl(d) is the inverse of the number + % of other detections (from other previously-selected detectors) that d overlaps with. + sovl=1./(1+sum(ovlp{i}(ismember(indata{i}(:,6),selected),:),1)); + % Add the values in sovl to the per-detector counts in cts. Note this + % can't be vectorized because one image may have repeated detectors. + for(t=1:numel(sovl)) + cts(indata{i}(t,6))=cts(indata{i}(t,6))+sovl(t); + end + end + cts(selected)=-Inf; + [coverageinc(j),selected(j)]=max(cts); + disp(['selected ' num2str(selected(j)) ' count ' num2str(coverageinc(j))]); + end + res=selected(:); + end + %keyboard +catch ex,dsprinterr;end +end diff --git a/hog/COPYING b/hog/COPYING new file mode 100755 index 0000000..8089dad --- /dev/null +++ b/hog/COPYING @@ -0,0 +1,21 @@ +Copyright (C) 2008, 2009, 2010 Pedro Felzenszwalb, Ross Girshick +Copyright (C) 2007 Pedro Felzenszwalb, Deva Ramanan + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/hog/features.cc b/hog/features.cc new file mode 100755 index 0000000..7587a76 --- /dev/null +++ b/hog/features.cc @@ -0,0 +1,234 @@ +#include +#include "mex.h" + +// small value, used to avoid division by zero +#define eps 0.0001 + +// unit vectors used to compute gradient orientation +double uu[9] = {1.0000, + 0.9397, + 0.7660, + 0.500, + 0.1736, + -0.1736, + -0.5000, + -0.7660, + -0.9397}; +double vv[9] = {0.0000, + 0.3420, + 0.6428, + 0.8660, + 0.9848, + 0.9848, + 0.8660, + 0.6428, + 0.3420}; + +static inline double min(double x, double y) { return (x <= y ? x : y); } +static inline double max(double x, double y) { return (x <= y ? y : x); } + +static inline int min(int x, int y) { return (x <= y ? x : y); } +static inline int max(int x, int y) { return (x <= y ? y : x); } + +// main function: +// takes a double color image and a bin size +// returns HOG features +mxArray *process(const mxArray *mximage, const mxArray *mxsbin) { + double *im = (double *)mxGetPr(mximage); + const int *dims = mxGetDimensions(mximage); + if (mxGetNumberOfDimensions(mximage) != 3 || + dims[2] != 3 || + mxGetClassID(mximage) != mxDOUBLE_CLASS) + mexErrMsgTxt("Invalid input"); + + int sbin = (int)mxGetScalar(mxsbin); + + // memory for caching orientation histograms & their norms + int blocks[2]; + blocks[0] = (int)round((double)dims[0]/(double)sbin); + blocks[1] = (int)round((double)dims[1]/(double)sbin); + double *hist = (double *)mxCalloc(blocks[0]*blocks[1]*18, sizeof(double)); + double *norm = (double *)mxCalloc(blocks[0]*blocks[1], sizeof(double)); + + // memory for HOG features + int out[3]; + out[0] = max(blocks[0]-2, 0); + out[1] = max(blocks[1]-2, 0); + out[2] = 27+4; + mxArray *mxfeat = mxCreateNumericArray(3, out, mxDOUBLE_CLASS, mxREAL); + double *feat = (double *)mxGetPr(mxfeat); + + int visible[2]; + visible[0] = blocks[0]*sbin; + visible[1] = blocks[1]*sbin; + + for (int x = 1; x < visible[1]-1; x++) { + for (int y = 1; y < visible[0]-1; y++) { + // first color channel + double *s = im + min(x, dims[1]-2)*dims[0] + min(y, dims[0]-2); + double dy = *(s+1) - *(s-1); + double dx = *(s+dims[0]) - *(s-dims[0]); + double v = dx*dx + dy*dy; + + // second color channel + s += dims[0]*dims[1]; + double dy2 = *(s+1) - *(s-1); + double dx2 = *(s+dims[0]) - *(s-dims[0]); + double v2 = dx2*dx2 + dy2*dy2; + + // third color channel + s += dims[0]*dims[1]; + double dy3 = *(s+1) - *(s-1); + double dx3 = *(s+dims[0]) - *(s-dims[0]); + double v3 = dx3*dx3 + dy3*dy3; + + // pick channel with strongest gradient + if (v2 > v) { + v = v2; + dx = dx2; + dy = dy2; + } + if (v3 > v) { + v = v3; + dx = dx3; + dy = dy3; + } + + // snap to one of 18 orientations + double best_dot = 0; + int best_o = 0; + for (int o = 0; o < 9; o++) { + double dot = uu[o]*dx + vv[o]*dy; + if (dot > best_dot) { + best_dot = dot; + best_o = o; + } else if (-dot > best_dot) { + best_dot = -dot; + best_o = o+9; + } + } + + // add to 4 histograms around pixel using linear interpolation + double xp = ((double)x+0.5)/(double)sbin - 0.5; + double yp = ((double)y+0.5)/(double)sbin - 0.5; + int ixp = (int)floor(xp); + int iyp = (int)floor(yp); + double vx0 = xp-ixp; + double vy0 = yp-iyp; + double vx1 = 1.0-vx0; + double vy1 = 1.0-vy0; + v = sqrt(v); + + if (ixp >= 0 && iyp >= 0) { + *(hist + ixp*blocks[0] + iyp + best_o*blocks[0]*blocks[1]) += + vx1*vy1*v; + } + + if (ixp+1 < blocks[1] && iyp >= 0) { + *(hist + (ixp+1)*blocks[0] + iyp + best_o*blocks[0]*blocks[1]) += + vx0*vy1*v; + } + + if (ixp >= 0 && iyp+1 < blocks[0]) { + *(hist + ixp*blocks[0] + (iyp+1) + best_o*blocks[0]*blocks[1]) += + vx1*vy0*v; + } + + if (ixp+1 < blocks[1] && iyp+1 < blocks[0]) { + *(hist + (ixp+1)*blocks[0] + (iyp+1) + best_o*blocks[0]*blocks[1]) += + vx0*vy0*v; + } + } + } + + // compute energy in each block by summing over orientations + for (int o = 0; o < 9; o++) { + double *src1 = hist + o*blocks[0]*blocks[1]; + double *src2 = hist + (o+9)*blocks[0]*blocks[1]; + double *dst = norm; + double *end = norm + blocks[1]*blocks[0]; + while (dst < end) { + *(dst++) += (*src1 + *src2) * (*src1 + *src2); + src1++; + src2++; + } + } + + // compute features + for (int x = 0; x < out[1]; x++) { + for (int y = 0; y < out[0]; y++) { + double *dst = feat + x*out[0] + y; + double *src, *p, n1, n2, n3, n4; + + p = norm + (x+1)*blocks[0] + y+1; + n1 = 1.0 / sqrt(*p + *(p+1) + *(p+blocks[0]) + *(p+blocks[0]+1) + eps); + p = norm + (x+1)*blocks[0] + y; + n2 = 1.0 / sqrt(*p + *(p+1) + *(p+blocks[0]) + *(p+blocks[0]+1) + eps); + p = norm + x*blocks[0] + y+1; + n3 = 1.0 / sqrt(*p + *(p+1) + *(p+blocks[0]) + *(p+blocks[0]+1) + eps); + p = norm + x*blocks[0] + y; + n4 = 1.0 / sqrt(*p + *(p+1) + *(p+blocks[0]) + *(p+blocks[0]+1) + eps); + + double t1 = 0; + double t2 = 0; + double t3 = 0; + double t4 = 0; + + // contrast-sensitive features + src = hist + (x+1)*blocks[0] + (y+1); + for (int o = 0; o < 18; o++) { + double h1 = min(*src * n1, 0.2); + double h2 = min(*src * n2, 0.2); + double h3 = min(*src * n3, 0.2); + double h4 = min(*src * n4, 0.2); + *dst = 0.5 * (h1 + h2 + h3 + h4); + t1 += h1; + t2 += h2; + t3 += h3; + t4 += h4; + dst += out[0]*out[1]; + src += blocks[0]*blocks[1]; + } + + // contrast-insensitive features + src = hist + (x+1)*blocks[0] + (y+1); + for (int o = 0; o < 9; o++) { + double sum = *src + *(src + 9*blocks[0]*blocks[1]); + double h1 = min(sum * n1, 0.2); + double h2 = min(sum * n2, 0.2); + double h3 = min(sum * n3, 0.2); + double h4 = min(sum * n4, 0.2); + *dst = 0.5 * (h1 + h2 + h3 + h4); + dst += out[0]*out[1]; + src += blocks[0]*blocks[1]; + } + + // texture features + *dst = 0.2357 * t1; + dst += out[0]*out[1]; + *dst = 0.2357 * t2; + dst += out[0]*out[1]; + *dst = 0.2357 * t3; + dst += out[0]*out[1]; + *dst = 0.2357 * t4; + } + } + + mxFree(hist); + mxFree(norm); + return mxfeat; +} + +// matlab entry point +// F = features(image, bin) +// image should be color with double values +void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { + if (nrhs != 2) + mexErrMsgTxt("Wrong number of inputs"); + if (nlhs != 1) + mexErrMsgTxt("Wrong number of outputs"); + plhs[0] = process(prhs[0], prhs[1]); +} + + + diff --git a/hog/features.mexa64 b/hog/features.mexa64 new file mode 100755 index 0000000..e064e48 Binary files /dev/null and b/hog/features.mexa64 differ diff --git a/indoor67_main.m b/indoor67_main.m new file mode 100755 index 0000000..7683afd --- /dev/null +++ b/indoor67_main.m @@ -0,0 +1,671 @@ +global ds; +myaddpath; +finishedinit=false; +try,finishedinit=dsload('.ds.finishedinit');catch,end +if(~finishedinit) + dscd('.ds'); + dsdelete('ds.*'); + % Edit the following with your path to the indoor67 data. This + % directory should contain directories for airport_inside, artstudio, + % etc. + indoor67path='/code/data/indoor67/'; + % optionally, set the web-accessible path for the same directory-- + % i.e. the path such that [weburl '/airport_inside/{imagename}'] can + % be used to download the image. This is only used in displays; + % if you just set it to the empty string, the code will still work, + % but some links in the displays will be broken. + weburl=''; + + % Set the number of parallel workers: + njobs=12; + % Set the amount of memory on each machine; dswork will estimate + % how many jobs can be run simultaneously on each machine + % based on this value. Note that if you're running jobs alongside + % the master, leave about 4GB for the master process + ds.conf.mempermachine=59; + % The machine where qsub can be run, or 'local' if you want to run on + % a single machine: + targmachine='local'; + + % Any additional configuration to pass to dsmapredopen(); + distprocconf=struct(); + + ds.masterscriptname=mfilename; + + % set an output directory (on a filesystem that's accessible to all matlab's) + dssetout('/PATH/TO/OUTPUT'); + % If you're running distributed and want to use SSH to send data between + % nodes, use the following to set a directory where dswork can cache + % files locally. On Starcluster, /mnt/sgeadmin is a good place (assuming + % you run your jobs as the user sgeadmin). + %dssetlocaldir('/mnt/sgeadmin/'); + + + if(~exist('dataset_indoor67_jpg.mat','file')) + disp(['WARNING: you are about to create jpeg versions of the gifs in the indoor67 data directory. '... + 'type ''return'' to continue or ''dbquit'' to cancel.']); + keyboard + preprocessindoor67(indoor67path,{'dataset_indoor67_test.mat','dataset_indoor67_jpg.mat'}); + end + testdata=load('dataset_indoor67_test.mat'); + setdataset(20,testdata.imgs,indoor67path,testdata.labelnames,weburl); + traindata=load('dataset_indoor67_jpg.mat'); + setdataset(19,traindata.imgs,indoor67path,traindata.labelnames,weburl); + + + if(isfield(ds.conf.gbz{ds.conf.currimset},'imgsurl')) + ds.imgsurl=ds.conf.gbz{ds.conf.currimset}.imgsurl; + end + rand('seed',1234) + ds.conf.params= struct( ... + 'ovlweight', 1, ... % use the inter-element communication scheme to set the weights. + 'negsperpos', 8, ... % during element training, the number of images we hard-mine + ... % negatives from during for each positive training image. + 'maxpixels',300000,... % large images will be downsampled to this many pixels. + 'minpixels',300000,... % small images will be upsampled to this many pixels. + 'patchCanonicalSize', {[64 64]}, ... % resolution for each detected patch. + 'scaleIntervals', 8, ... % number of pyramid levels per scale. + 'sBins', 8, ... % pixel width/height for HOG cells + 'useColor', 1, ... % include Lab tiny images in the descriptor for a patch. + 'whitening', 1, ... % whiten the patch features + 'normbeforewhit', 1, ... % mean-subtract and normalize features before applying whitening + 'normalizefeats', 1, ... % mean-subtract and normalize features after applying whitening + 'graddescfun', @doGradDescentproj, ... % function ptr for the optimization function. It gets called + ... % on each round of the optimization, including during + ... % initialization. See doGradDescentproj.m + 'stepsize', .05, ... % step size used by the optimizer + 'beta', 1, ... % beta value for the optimization + 'optimizationComputeLimit',1500,... % maximum number of vector-matrix multiplies that the + ... % optimizer may perform on each training iteration + 'samplingOverlapThreshold', 0.6, ... % patches sampled initially can't have overlap larger + ... % than this value. + 'samplingNumPerIm',20,... % sample this many patches per image. + 'multperim', 1, ... % allow multiple detections per image + 'nmsOverlapThreshold', 0.4 ... % overlap threshold for NMS during detection. + ) + if(~dsbool(ds.conf.params,'ovlweight')) + ds.conf.params.lambdainit=.002;% lambda value for the optimization used during the first + % training round (gets increased proportional to the number + % of samples at later training rounds) + else + ds.conf.params.lambdainit=.02; % without the inter-element communication, everything + % has a higher weight, and so we need to increase the + % regularization. + end + + + %pick which images to use out of the dataset + imgs=ds.imgs{ds.conf.currimset}; + ds.myiminds=1:numel(imgs.label); + + % ds.roundinds stores the indices of the images to use at each iteration of training + [~,cls]=ismember(ds.imgs{ds.conf.currimset}.label(ds.myiminds),unique(ds.imgs{ds.conf.currimset}.label)); + imgsbyclass=distributeby(ds.myiminds(:),cls); + rp=randperm(numel(ds.myiminds)); + % the first 3 rounds of training are just used to set the initial bandwidth, so we + % use a very small subset of them. + % actually it's pretty stupid that this takes 3 rounds; it could be done in 1 except + % that doing so would generate tons of useless feature vectors in the current pipeline. + ds.roundinds{1}=ds.myiminds(rp(1:20)); + ds.roundinds{2}=ds.myiminds(rp(21:60)); + ds.roundinds{3}=ds.myiminds(rp(61:120)); + % evenly divide the rest of the images. Note that the classes aren't quite balanced, + % so the last round deals with extra/missing images. + for(i=4:8) + ds.roundinds{i}=[]; + for(j=1:numel(imgsbyclass)) + if(i==8) + ul=numel(imgsbyclass{j}); + else + ul=16; + end + ds.roundinds{i}=[ds.roundinds{i};imgsbyclass{j}(1:ul)]; + imgsbyclass{j}(1:ul)=[]; + end + ds.roundinds{i}=ds.roundinds{i}(randperm(numel(ds.roundinds{i}))); + end + if(~dsmapredisopen()) + dsmapredopen(njobs,targmachine,distprocconf); + disp('waiting 10 sec for mapreducers to start...') + pause(10) + end + + % Generate the whitening matrix based on 1500 randomly sampled images. + ds.aggcov.myiminds=ds.myiminds(rp(1:min(numel(rp),1500)));; + dssave; + dscd('ds.aggcov'); + dsrundistributed('aggregate_covariance',{'ds.myiminds'},struct('allatonce',1,'noloadresults',1,'maxperhost',max(1,floor(ds.mempermachine/4)))); + total=0; + clear featsum dotsum; + dsload('ds.n'); + for(i=1:numel(ds.n)) + if(isempty(ds.n{i})),continue;end + total=total+dsload(['ds.n{' num2str(i) '}'],'clear'); + if(~exist('dotsum','var')) + dotsum=dsload(['ds.dotsum{' num2str(i) '}'],'clear'); + else + dotsum=dotsum+dsload(['ds.dotsum{' num2str(i) '}'],'clear'); + end + if(~exist('featsum','var')) + featsum=dsload(['ds.featsum{' num2str(i) '}'],'clear'); + else + featsum=featsum+dsload(['ds.featsum{' num2str(i) '}'],'clear'); + end + if(any(isnan(dotsum(:)))||any(isnan(featsum(:)))) + keyboard; + end + disp(i); + end + covmat=(dotsum./total-(featsum'./total)*(featsum./total)); + covmat=covmat+.01*eye(size(covmat,1)); + dscd('.ds'); + ds.datamean=featsum./total; + disp('performing matrix square root...'); + ds.invcovmat=inv(covmat); + ds.whitenmat=sqrtm(ds.invcovmat); + clear featsum dotsum total; + clear covmat; + dsdelete('ds.aggcov'); +end +ds.finishedinit=true; +dssave; + +initmodes=false; +try,initmodes=dsload('.ds.initmodes');catch,end +if(~initmodes) + disp('sampling positive patches'); + dscd('.ds'); + setdataset(19); + dsdelete('ds.round*'); + dsdelete('ds.sample'); + dsdelete('ds.classperbatch'); + dsdelete('ds.detectors'); + dsdelete('ds.initFeats'); + dsdelete('ds.initPatches'); + dsdelete('ds.batchfordetr'); + dsdelete('ds.trainingrounds'); + extrparams=ds.conf.params; + initFeatsExtra=[]; + initPatsExtra=[]; + ds.sample=struct(); + ds.sample.initInds=ds.myiminds; + dsrundistributed('[ds.sample.patches{dsidx}, ds.sample.feats{dsidx}]=sampleRandomPatchesbb(ds.sample.initInds(dsidx),20);',{'ds.sample.initInds'},struct('maxperhost',max(1,floor(ds.mempermachine/4)))); + + % divide the sampled patches into batches (two images' worth of sampled + % patches per batch). The batches are purely for efficiency, specifically + % to limit the number of files that get written. Note that each + % batch needs to have a single class label. + batchsz=cellfun(@(x) size(x,1),ds.sample.patches)'; + ds.classperbatch=ds.imgs{ds.conf.currimset}.label(ds.sample.initInds); + [batchsz,cpb]=distributeby(batchsz,ds.classperbatch); + for(i=1:numel(batchsz)) + if(mod(numel(batchsz{i}),2)==0) + batchsz{i}=batchsz{i}(1:2:end)+batchsz{i}(2:2:end); + else + batchsz{i}=[batchsz{i}(1:2:end-1)+batchsz{i}(2:2:end-1); batchsz{i}(end)]; + end + cpb2{i,1}=repmat(cpb(i),numel(batchsz{i}),1); + end + batchsz=cell2mat(batchsz)'; + % store the class label for each batch. + ds.classperbatch=cell2mat(cpb2); + ds.classperbatch(batchsz==0)=[]; + batchsz(batchsz==0)=[]; + initPatches=[structcell2mat(ds.sample.patches(:))]; + initPatches=[initPatsExtra(:); initPatches]; + disp(['sampled ' num2str(size(initPatches,1)) ' patches']); + ds.initFeats=cell2mat(ds.sample.feats'); + ds.initFeats=[initFeatsExtra; ds.initFeats]; + + % convert the patch features for each batch into a detector structure. + ds.detectors=cellfun(@(x,y,z) struct('w',x,'b',y,'id',z),... + mat2cell([ds.initFeats],batchsz,size(ds.initFeats,2)),... + mat2cell(repmat(1,size(ds.initFeats,1),1),batchsz,1),... + mat2cell((1:size(ds.initFeats,1))',batchsz,1),'UniformOutput',false)'; + initPatches(1:sum(batchsz),6)=1:size(ds.initFeats,1); + ds.initPatches=initPatches; + % batchfordetr is an n-by-2 detector for the n detectors: column 1 is + % a detector id, column 2 is the index of the batch containing it. + marks=zeros(1:size(ds.initFeats,1),1); + marks(cumsum(batchsz)+1)=1; + marks(end)=[]; + marks(1)=1; + ds.batchfordetr=[(1:size(ds.initFeats,1))' cumsum(marks)]; + dssave(); + dsdelete('ds.sample') + + if(exist([ds.masterscriptname '_wait'],'file')) + keyboard; + end + + % initialize the set of detectors: this will only update the b value. + ds.initFeats=[]; + runset=ds.sys.distproc.availslaves; + dsrundistributed('autoclust_opt_init',{'ds.detectors'},struct('noloadresults',1,'maxperhost',max(1,floor(ds.mempermachine/4)),'forcerunset',runset)); +end +ds.initmodes=true; +dssave; + +trainingrounds=false; +try,trainingrounds=dsload('.ds.trainingrounds');catch,end +if(~trainingrounds) + dscd('.ds'); + setdataset(19); + dsdelete('ds.gendpoolfeats'); + roundid=1; + while(isfield(ds,['round' num2str(roundid)])) + roundid=roundid+1; + end + uniquelabels=1:numel(ds.conf.gbz{ds.conf.currimset}.labelnames); + ds.uniquelabels=uniquelabels(:)'; + while(roundid<=(numel(ds.roundinds))) + thisroundmapreduce=false; + try,thisroundmapreduce=dsload('.ds.round.thisroundmapreduce');catch,end + if(~thisroundmapreduce) + dsdelete('ds.round.myiminds'); + dsdelete('ds.round.roundid'); + dsdelete('ds.round.ndetrounds'); + dsdelete('ds.round.lambda'); + dsdelete('ds.round.beta'); + dsdelete('ds.round.newfeat'); + try + dsdelete(['ds.round.newdets{' num2str(roundid) '}{1:' num2str(dssavestatesize('ds.round.newdets',2)) '}']); + catch,end + dsdelete('ds.nextround'); + thistraininground=false; + try,trainingrounds=dsload('.ds.trainingrounds');catch,end + ds.round.myiminds=ds.roundinds{roundid}; % images to run training on + ds.round.ndetrounds=max(roundid-3,1); % the number of real detection rounds we've completed + ds.round.roundid=roundid; + if(~isfield(ds.round,'detrgroup')) + % make a fake clustering for the first few rounds, where every patch gets its own cluster. + ds.round.detrgroup=[ds.batchfordetr(:,1), (1:size(ds.batchfordetr,1))']; + end + if(roundid<=3) + mph=max(1,floor(ds.mempermachine/4)); + elseif(roundid<=4) + mph=max(1,floor(ds.mempermachine/2)); + else + mph=max(1,floor(ds.mempermachine/1.5)); + end + if(mod(roundid,1)==0) + % matlab's memory footprint grows even if it's not using the memory; restarting frees it. + dsmapredrestart; + end + if(roundid>=4) + % increase lambda (the bandwidth) proportional to the number of images we've run detection on. + ds.round.lambda=(roundid-3)*ds.conf.params.lambdainit; + else + % if we're initializing, beta determines how large rho is. Since we initialize on a small + % set, we want to make beta artificially small so that the detector will fire less + % when it starts doing detection for real. This reduces the number of useless patch features + % get generated. + ds.round.beta=ds.conf.params.beta/3; + end + %end + + % the main mapreduce that runs detectors on images and sends the detected feature vectors + % to reducers, each of which optimizes one batch of detectors. We use forcerunset to + % make sure each detector is always optimized on the same machine, since these machines + % cache features locally. + dsmapreduce(['detectors=dsload(''ds.round.detectors'')'';'... + 'imgs=dsload(''ds.imgs{ds.conf.currimset}'');'... + 'dsload(''ds.classperbatch'');'... + 'posbats=find(imgs.label(ds.round.myiminds(dsidx))==ds.classperbatch);'... % run all detectors whose class matches the class of this image + 'negbats=find(imgs.label(ds.round.myiminds(dsidx))~=ds.classperbatch);'... + 'rp=randperm(numel(negbats));'... + 'negbats=negbats(rp(1:min(numel(negbats),numel(posbats)*ds.conf.params.negsperpos)));'... % run a random subset of the detectors for other classes + '[dets,feats]=detectInIm(effstrcell2mat(detectors([posbats(:); negbats(:)])),'... + 'ds.round.myiminds(dsidx),'... + 'struct(''thresh'',-.02/dsload(''ds.round.ndetrounds''),'... + '''multperim'',dsload(''ds.round.roundid'')>2,'... + '''flipall'',true));' ... + 'ctridx=dsload(''ds.batchfordetr'');'... + 'dsload(''ds.round.detrgroup'');'... + '[~,detrgroupord]=ismember(ds.round.detrgroup(:,1),ctridx(:,1));'... + 'ovlweight=overlapReweightForImg(dets,[ctridx(:,1) ds.classperbatch(ctridx(:,2)) ds.round.detrgroup(detrgroupord,2)]);'...% ovlweights are the alpha_i,j from the paper + 'ds.round.newfeat(1:numel(unique(ctridx(:,2))),dsidx)={struct(''assignedidx'',[],''feat'',[])};'... + 'if(~isempty(dets)),'... + '[~,ctrpos]=ismember(dets(:,6),ctridx(:,1));'... + '[dets,feats,ovlweight,outpos]=distributeby(dets,single(feats),ovlweight,ctridx(ctrpos,2));'... + 'ds.round.newfeat(outpos,dsidx)=cellfun(@(x,y,z) struct(''assignedidx'',x,''feat'',y,''ovlweights'',z),dets,feats,ovlweight,''UniformOutput'',false);'... + 'end']... + ,'autoclust_optimize',{'ds.round.myiminds'},'ds.round.newfeat',struct('noloadresults',1,'forcerunset',runset),struct('maxperhost',mph),struct('maxperhost',max(1,floor(ds.mempermachine/4)))); + end + dsdelete('ds.round.component'); + dsdelete('ds.nextround.detrgroup'); + dsdelete('ds.nextround.reweights'); + dsdelete('ds.nextround.detsbyim'); + dsdelete('ds.nextround.prevweights'); + + % Now that we've trained the detectors, we need to re-compute the + % alpha values for each patch. + if(roundid>=4 && dsbool(ds.conf.params,'ovlweight')) + + % findOverlapping3 finds overlapping clusters--i.e. it performs + % the agglomerative element clustering step described in the paper. + dsrundistributed(['dsload(''ds.classperbatch'');dsload(''ds.batchfordetr'');'... + '[~,~,ds.round.component{dsidx}]='... + 'findOverlapping3(''ds.nextround.prevdets'',find(ds.classperbatch==ds.uniquelabels(dsidx)),'... + '[ds.batchfordetr(:,1),ds.classperbatch(ds.batchfordetr(:,2))],'... + 'struct(''ndetsforoverlap'',.5,''maxoverlaps'',3,''clusterer'',''agglomerative''))'],'ds.uniquelabels',struct('maxperhost',max(1,floor(ds.mempermachine/3)))); + + % Construct the detrgroup matrix, which is an two-column matrix, The left column + % is the detector id, the right column specifies which cluster that element + % belongs to. + component=[]; + toadd=0; + for(i=1:numel(ds.round.component)) + tmpcomponent=ds.round.component{i}; + tmpcomponent(:,2)=tmpcomponent(:,2)+toadd; + component=[component;tmpcomponent]; + toadd=max(component(:,2)); + end + [~,cord]=ismember(ds.batchfordetr(:,1),component(:,1)); + component=component(cord,:); + ds.nextround.detrgroup=component(:,1:2); + + % Collect the detections for each image and compute the alpha values. Note + % that if ds.conf.params.ovlweight is false or doesn't exist, then the + % clustering generated above is ignored and weights are simply assigned + % based on the number of detections for a given element in the image. + detsbyim=cell2mat(dsload('ds.nextround.prevdets','clear')'); + [detsbyim,~,ord]=distributeby(detsbyim,detsbyim(:,7)); + ds.nextround.detsbyim=detsbyim'; + clear detsbyim; + dsrundistributed(['ctridx=dsload(''ds.batchfordetr'');'... + 'dsload(''ds.classperbatch'');'... + 'dsload(''ds.nextround.detrgroup'');'... + '[~,detrgroupord]=ismember(ds.nextround.detrgroup(:,1),ctridx(:,1));'... + 'ds.nextround.reweights{dsidx}=overlapReweightForImg(ds.nextround.detsbyim{dsidx},[ctridx(:,1) ds.classperbatch(ctridx(:,2)) ds.nextround.detrgroup(detrgroupord,2)]);'],'ds.nextround.detsbyim'); + ds.nextround.detsbyim={}; + dsload('ds.nextround.prevdets'); + reweights=mat2cell(invertdistributeby(ds.nextround.reweights(:),ord),cellfun(@(x) size(x,1),ds.nextround.prevdets),1); + classfordetr(ds.batchfordetr(:,1))=ds.classperbatch(ds.batchfordetr(:,2)); + + % The alpha value for the patch that initialized each cluster doesn't + % follow the same rules as everything else, since we don't know when + % the image it was sampled from will actually have detectors run + % on it. Hence, we instead start with a value of 1 and decay + % exponentially toward the mean of the other weights. I don't believe + % this is important in the current implementation, but I haven't really + % tried other approaches to setting this weight. + for(i=1:numel(reweights)) + [currdets,currweights,detid,ord]=distributeby(ds.nextround.prevdets{i},reweights{i},ds.nextround.prevdets{i}(:,6)); + for(j=1:numel(currdets)) + ispos=find(ds.imgs{ds.conf.currimset}.label(currdets{j}(:,7))==c(classfordetr(currdets{j}(:,6)))); + if(ispos(1)~=1) + error('detections got out of order'); + end + if(numel(ispos)>1) + currweights{j}(1)=.5^(roundid-4)+(1-.5^(roundid-4))*mean(currweights{j}(ispos(2:end))); + end + end + reweights{i}=invertdistributeby(currweights,ord); + end + ds.nextround.prevweights=reweights(:)'; + end + dsdelete(['ds.progressdisplay' num2str(roundid)]); + + % Generate a visualization of the progress. + if(roundid>=4) + % select a subset of the batches to display, since there's too many + % detectors overall. + batchestodisp=1:40:numel(unique(ds.batchfordetr(:,2))); + batchestodisp=batchestodisp(1:min(10,numel(batchestodisp))); + dets=cell2mat(dsload(['ds.nextround.prevdets{' num2str(batchestodisp) '}'])'); + ovlweights=cell2mat(dsload(['ds.nextround.prevweights{' num2str(batchestodisp) '}'])'); + [dets,ovlweights]=distributeby(dets,ovlweights,dets(:,6)); + for(i=1:numel(dets)) + [~,ord]=sort(dets{i}(:,5),'descend'); + ovlweights{i}=ovlweights{i}(ord(1:min(20,size(dets{i},1)))); + dets{i}=dets{i}(ord(1:min(20,size(dets{i},1))),:); + end + dets=cell2mat(dets); + ovlweights=cell2mat(ovlweights); + dsup(['ds.progressdisplay' num2str(roundid) '.patchimg'],extractpatches(dets)); + conf=struct('dets',dets,'detrord',ds.batchfordetr(ismember(ds.batchfordetr(:,2),batchestodisp),1)); + if(dsbool(ds.conf.params,'ovlweight')) + conf.ovlweights=ovlweights; + end + mhprender('patchdisplay.mhp',['ds.progressdisplay' num2str(roundid) '.displayhtml'],conf); + fail=1;while(fail),try + dssave; + fail=0;catch ex,if(fail>5),rethrow(ex);end,fail=fail+1;end,end + dsclear(['ds.progressdisplay' num2str(roundid)]); + end + ds.round=struct(); + dsmv('ds.round',['ds.round' num2str(roundid)]); + if(roundid>4) + dsdelete(['ds.round' num2str(roundid)]); + end + dsmv('ds.nextround','ds.round');%keep the stub--it's how we measure progress! + roundid=roundid+1; + end + ds.trainingrounds=true; + dssave; +end + +gendpoolfeats=false; +try,gendpoolfeats=dsload('.ds.gendpoolfeats');catch,end +if(~gendpoolfeats) + dscd('.ds'); + dsdelete('ds.scores'); + dsdelete('ds.finids'); + dsdelete('ds.finmodel'); + dsdelete('ds.testpoolfeats'); + dsdelete('ds.poolfeats'); + dsdelete('ds.display_*'); + setdataset(19); + % The element training is done. Now we need to select the top elements + % for each class. Note that we perform the selection based on + % ds.newdets, which we obtained by firing the detectors on images they + % hadn't (yet) been trained on, so it's a roughly unbiased estimate + % of their held out accuracy, but the scores from different rounds + % of detections are not comparable. + uniquelabels=1:numel(ds.conf.gbz{ds.conf.currimset}.labelnames); + ds.uniquelabels=uniquelabels(:)'; + dsrundistributed(['dsload(''ds.batchfordetr'');dsload(''ds.classperbatch'');dsload(''ds.imgs'');dsload(''ds.roundinds'');'... + 'if(sum(ds.classperbatch==ds.uniquelabels(dsidx))==0),return;end,'... + 'dsload([''ds.newdets{'' num2str(numel(ds.roundinds)-2:numel(ds.roundinds)) ''}{'' num2str(find(ds.classperbatch(:)''==ds.uniquelabels(dsidx))) ''}'']);'... + 'detsbyround={};'... + 'for(i=numel(ds.roundinds)-1:numel(ds.roundinds)),'... + 'detsbyround{end+1,1}=structcell2mat(ds.newdets(i,:)'');'... + 'end,'... + '[ds.finids{dsidx},ds.scores{dsidx}]=greedySelectDetrsCoverage(detsbyround,ds.imgs{ds.conf.currimset}.label==ds.uniquelabels(dsidx),.7,200,struct(''useoverlap'',1));'... + 'ds.newdets={}'... + ],'ds.uniquelabels',struct('maxperhost',max(1,floor(ds.mempermachine/4)))); + + % Collect detections from the held out detectors and generate + % a display for each class. Note that this display isn't the same + % as the one on the webpage. This one displays held out detections + % using the greedy ranking, whereas the one on the webpage displays + % detections after training, only includes detections from the + % positive class, and ranks based on the SVM weight vectors that we haven't + % even generated yet. + heldoutdets={}; + for(i=numel(ds.roundinds)-2:numel(ds.roundinds)) + newdets=cell2mat(dsload(['ds.newdets{' num2str(i) '}{1:' num2str(dssavestatesize('ds.newdets',2)) '}'])'); + ds.newdets={}; + newdets(~ismember(newdets(:,6),cell2mat(ds.finids(:))),:)=[]; + heldoutdets{end+1,1}=newdets; + end + heldoutdets=cell2mat(heldoutdets(:)); + + [heldoutdets,detid]=distributeby(heldoutdets,heldoutdets(:,6)); + for(i=1:numel(ds.finids)) + heldoutdetsbyclass=heldoutdets(ismember(detid,ds.finids{i})); + ds.topk{i}=cell2mat(maxkall(heldoutdetsbyclass,5,20)); + end + ds.classes=ds.conf.gbz{ds.conf.currimset}.labelnames(:)'; + dsrundistributed(['if(isempty(ds.finids{dsidx})),return;end,'... + 'dsup([''ds.display_'' ds.classes{dsidx} ''.patchimg''],extractpatches(ds.topk{dsidx}));'... + 'conf=struct(''dets'',ds.topk{dsidx},'... + '''detrord'',ds.finids{dsidx},'... + '''message'',{cellfun(@(x) [''score:'' num2str(x)],num2cell(ds.scores{dsidx}),''UniformOutput'',false)});'... + 'mhprender(''patchdisplay.mhp'',[''ds.display_'' ds.classes{dsidx} ''.displayhtml''],conf);']... + ,{'ds.finids','ds.scores','ds.topk','ds.classes'},struct('noloadresults',true)); + + % save out the final selected element detectors for easy access. + model=effstrcell2mat(dsload('ds.round.detectors','clear')'); + model=selectdetrs2(model,cell2mat(ds.finids(:))); + ds.finmodel=model; + dssave; + + % generate a feature vector for each training image + dsrundistributed('dsload(''ds.finmodel'');ds.poolfeats{dsidx}=distGenPooledFeats(ds.finmodel,ds.myiminds(dsidx))',{'ds.myiminds'},struct('noloadresults',true)); + trainlab=ds.imgs{ds.conf.currimset}.label; + % switch to the testing set and + setdataset(20); + ds.mytestinds=1:numel(ds.imgs{ds.conf.currimset}.fullname); + dsrundistributed('dsload(''ds.finmodel'');ds.testpoolfeats{dsidx}=distGenPooledFeats(ds.finmodel,ds.mytestinds(dsidx))',{'ds.mytestinds'},struct('noloadresults',true)); +end +setdataset(19); +dscd('.ds'); + +% optionally load the ifv features generated by Junega, Vedaldi, Jawahar & +% Zisserman 2013's implementation of ifv. Note that that this takes about +% 5GB of extra memory, since we blindly load a 100,000-dimensional feature +% vector for every image. +if(0) + % Note also we're assuming here that the ifv IMDB order is the same as + % our order (since the ifv output doesn't actually include the IMDB :-/) + % This will be true if the order of directory listings is deterministic, + % since the IMDB gets their file listing in the same way we do. However, + % the madlab docs say that the order returned by dir actually depends on + % the OS. + ifvpermutation=1:10000; + ifvpath='/PATH/TO/IFV'; + fils=cleandir([ifvpath '/data/codes/FKtest_comb_train_chunk*']); + for(i=1:numel(fils)) + load([ifvpath '/data/codes/' fils(i).name]); + [~,idx]=ismember(index,ifvpermutation); + for(j=1:numel(idx)) + trainifvfeats(idx(j),:)=chunk(:,j)'; + end + disp(i) + end + ifvkern=trainifvfeats*trainifvfeats'; + + fils=cleandir([ifvpath '/data/codes/FKtest_comb_test_chunk*']); + for(i=1:numel(fils)) + load([ifvpath '/data/codes/' fils(i).name]); + [~,idx]=ismember(index,ifvpermutation); + for(j=1:numel(idx)) + testifvFeats(idx(j),:)=chunk(:,j)'; + end + disp(i) + end + ifvtestkern=trainifvfeats*testifvFeats'; + pfwt=.01; + thresh=.6; + ifvwt=18; +else + ifvkern=0; + ifvtestkern=0; + ifvkern=0; + ifvtestkern=0; + if(dsbool(ds.conf.params,'ovlweight')) + pfwt=.012; + thresh=.7; + ifvwt=0; + else + pfwt=.2; + thresh=.3; + ifvwt=0; + end +end + + + +trainpf2=cell2mat(dsload('ds.poolfeats','clear')'); +testpf2=cell2mat(dsload('ds.testpoolfeats','clear')'); +% the kernel function for the SVM. It's actually a linear kernel after +% a rectified-linear feature transform. +kernfun=@(x,y) ((max(x+thresh,0))*(max(y+thresh,0))'); +% the transformation that happens before the dot product in the kernel. +transfun=@(x) max(x+thresh,0); + +% Everything is much more efficient if we work directly with the kernel matrix. +pfkern=kernfun(trainpf2,trainpf2); +kernmat=ifvkern*ifvwt+pfkern*pfwt; +classes=unique(trainlab) + +% optionally you can open a matlabpool here, but if your'e using +% dswork your probably already have a lot of matlabs running on this machine. +% This honestly doesn't take that long, though. +% matlabpool open 12 +parfor(i=1:numel(classes)) + inds=find(trainlab==classes(i)); + label=-ones(size(kernmat,1),1); + label(inds)=1; + trainedsvm{i}=svmtrain(label,double([(1:numel(label))' kernmat]),'-s 0 -t 4 -c .1 -h 0'); + disp(i) +end + +% compute the scores for the testing images. +pftestkern=kernfun(trainpf2,testpf2); +traintestkern=ifvtestkern*ifvwt+pftestkern*pfwt; +testscr=[]; +for(i=1:numel(trainedsvm)) + testscr(i,:)=(trainedsvm{i}.sv_coef'*traintestkern(trainedsvm{i}.SVs,:)-trainedsvm{i}.rho)*trainedsvm{i}.Label(1); +end + +[~,label]=max(testscr,[],1); +truth=ds.imgs{ds.conf.currimset}.label'; +% here we print the final performance. +perf=sum(truth==label)./numel(label) +dsdelete('ds.svm'); +% Finally we generate heatmaps. +for(i=1:numel(classes)) + ds.svm{i}=getMinimalModel2(trainedsvm{i},transfun(trainpf2)); +end +allscores=testscr'; + +dsdelete('ds.dispdets'); +dsdelete('ds.easyimages'); +dsdelete('ds.errorimages'); +prevdets=cell2mat(dsload('ds.round.prevdets','clear')'); +prevdets=distributeby(prevdets,prevdets(:,6)); +ds.dispdets=cell2mat(maxkall(prevdets(:),5,5)); +clear prevdets; +ds.dispdets=ds.dispdets(ismember(ds.dispdets(:,6),ds.finmodel.id),:); +dssave; +% there are two versions of the heatmap: first, we generate heatmaps for +% the most confident correctly-classified images, and then for the most confident +% errors. Note that the code below generates heatmaps for the detections +% that *lowered* the SVM score as well as those that raised it. However, we +% omitted the ones that lowered the detection score from the website since +% they are harder to interpret. +for(disperror=[0 1]) + if(disperror) + dscd('.ds.errorimages'); + errorval=allscores(sub2ind(size(allscores),(1:size(allscores,1))',label(:)))-allscores(sub2ind(size(allscores),(1:size(allscores,1))',truth(:))); + ds.cls=[truth(:) label(:)] + else + dscd('.ds.easyimages'); + for(i=1:size(allscores,1)) + [scr,ds.cls(i,:)]=maxk(allscores(i,:),2); + errorval(i)=scr(1)-scr(2); + end + end + + [confidence,ds.todisp]=maxk(errorval,100); + ds.transfun=transfun; + setdataset(20); + + dsrundistributed(['detrs=dsload(''.ds.finmodel'');svm=dsload(''.ds.svm'');transfun=dsload(''ds.transfun'');'... + 'cls=dsload(''ds.cls'');dispdets=dsload(''.ds.dispdets'');dsload(''.ds.imgs'');'... + 'im=im2double(getimg(ds.todisp(dsidx)));'... + 'ds.origimg{dsidx}=im;'... + '[ds.leftposimg{dsidx},ds.leftnegimg{dsidx}]=dispClassifier('... + 'detrs,im,svm{cls(ds.todisp(dsidx),1)},transfun,dispdets,[''ds.display_left_'' num2str(dsidx)],19);'... + '[ds.rightposimg{dsidx},ds.rightnegimg{dsidx}]=dispClassifier('... + 'detrs,im,svm{cls(ds.todisp(dsidx),2)},transfun,dispdets,[''ds.display_right_'' num2str(dsidx)],19);'],'ds.todisp',struct('noloadresults',1)); + + mhprender('errdisp.mhp','ds.errhtml',struct('trueclasses',{ds.conf.gbz{ds.conf.currimset}.labelnames(ds.cls(ds.todisp,1))},'guessclasses',{ds.conf.gbz{ds.conf.currimset}.labelnames(ds.cls(ds.todisp,2))},'confidence',confidence,'iserror',disperror)); + dssave; +end + +dscd('.ds'); +perf=sum(truth==label)./numel(label) diff --git a/linkage2.m b/linkage2.m new file mode 100755 index 0000000..2bec2ca --- /dev/null +++ b/linkage2.m @@ -0,0 +1,456 @@ +function Z = linkage(Y, method, pdistArg, varargin) +%LINKAGE Create hierarchical cluster tree. +% Z = LINKAGE(X), where X is a matrix with two or more rows, creates a +% matrix Z defining a tree of hierarchical clusters of the rows of X. +% Clusters are based on the single linkage algorithm using Euclidean +% distances between the rows of X. Rows of X correspond to observations +% and columns to variables. +% +% Z = LINKAGE(X,METHOD) creates a hierarchical cluster tree using the +% the specified algorithm. The available methods are: +% +% 'single' --- nearest distance (default) +% 'complete' --- furthest distance +% 'average' --- unweighted average distance (UPGMA) (also known as +% group average) +% 'weighted' --- weighted average distance (WPGMA) +% 'centroid' --- unweighted center of mass distance (UPGMC) +% 'median' --- weighted center of mass distance (WPGMC) +% 'ward' --- inner squared distance (min variance algorithm) +% +% Z = LINKAGE(X,METHOD,METRIC) performs clustering based on the distance +% metric METRIC between the rows of X. METRIC can be any of the distance +% measures accepted by the PDIST function. The default is 'euclidean'. +% For more information on PDIST and available distances, type HELP PDIST. +% The 'centroid', 'median', and 'ward' methods are intended only for the +% Euclidean distance metric. +% +% Z = LINKAGE(X,METHOD,PDIST_INPUTS) enables you to pass extra input +% arguments to PDIST. PDIST_INPUTS should be a cell array containing +% input arguments to be passed to PDIST. +% +% Z = LINKAGE(X,METHOD,METRIC,'savememory',VALUE) specifies the value for +% the optional argument 'savememory'. VALUE is a string 'on' or 'off'. +% When VALUE is 'on', LINKAGE performs clustering without computing the +% distance matrix internally, so it usually requires less memory for data +% with large number of observations. VALUE 'on' requires both of the +% following two conditions to be satisfied: +% * METHOD is 'ward', 'centroid', or 'median'. +% * METRIC is 'euclidean'. +% When the above two conditions are satisfied, the default is 'on' when +% the number of columns in X is not greater than 20, or the machine +% doesn't have enough memory to save the distance matrix. +% +% Z = LINKAGE(Y) and Z = LINKAGE(Y,METHOD) are alternative syntaxes that +% accept a vector representation Y of a distance matrix. Y may be a +% distance matrix as computed by PDIST, or a more general dissimilarity +% matrix conforming to the output format of PDIST. +% +% The output matrix Z contains cluster information. Z has size m-1 by 3, +% where m is the number of observations in the original data. Column 1 +% and 2 of Z contain cluster indices linked in pairs to form a binary +% tree. The leaf nodes are numbered from 1 to m. They are the singleton +% clusters from which all higher clusters are built. Each newly-formed +% cluster, corresponding to Z(i,:), is assigned the index m+i, where m is +% the total number of initial leaves. Z(i,1:2) contains the indices of +% the two component clusters which form cluster m+i. There are m-1 higher +% clusters which correspond to the interior nodes of the output +% clustering tree. Z(i,3) contains the corresponding linkage distances +% between the two clusters which are merged in Z(i,:), e.g. if there are +% total of 30 initial nodes, and at step 12, cluster 5 and cluster 7 are +% combined and their distance at this time is 1.5, then row 12 of Z will +% be (5,7,1.5). The newly formed cluster will have an index 12+30=42. If +% cluster 42 shows up in a latter row, that means this newly formed +% cluster is being combined again into some bigger cluster. +% +% The 'centroid' and 'median' methods can produce a cluster tree that is +% not monotonic. This occurs when the distance from the union of two +% clusters, r and s, to a third cluster is less than the distance between +% r and s. In such a case, in a dendrogram drawn with the default +% orientation, the path from a leaf to the root node takes some downward +% steps. You may want to use another method when that happens. +% +% You can provide the output Z to other functions including DENDROGRAM to +% display the tree, CLUSTER to assign points to clusters, INCONSISTENT to +% compute inconsistent measures, and COPHENET to compute the cophenetic +% correlation coefficient. +% +% Examples: +% % Compute four clusters of the Fisher iris data using Ward linkage +% % and ignoring species information, and see how the cluster +% % assignments correspond to the three species. +% load fisheriris +% Z = linkage(meas,'ward','euclidean'); +% c = cluster(Z,'maxclust',4); +% crosstab(c,species) +% dendrogram(Z) +% +% % Create a hierarchical cluster tree for a data with 20000 +% % observations using Ward linkage. If you set the value of +% % 'savememory' to 'off', you may get an out-of-memory error if your +% % machine doesn't have enough memory to hold the distance matrix. +% X = rand(20000,3); +% Z = linkage(X,'ward','euclidean','savememory', 'on') +% +% See also PDIST, INCONSISTENT, COPHENET, DENDROGRAM, CLUSTER, +% CLUSTERDATA, KMEANS, SILHOUETTE. + +% Copyright 1993-2011 The MathWorks, Inc. +% $Revision: 1.1.8.10 $ + +% Check for size and type of input +[k, n] = size(Y); +m = ceil(sqrt(2*n)); % m = (1+sqrt(1+8*n))/2, but works for large n +if k>1 % data matrix input + if nargin<2 + method = 'single'; + end + if nargin<3 + pdistArg = 'euclidean'; + end + nargs = 3; +else % distance matrix input or bad input + nargs = nargin; +end + +if nargs>=3 % should be data input + if k == 1 && m*(m-1)/2 == n + warning(message('stats:linkage:CallingPDIST')); + end + if k < 2 + error(message('stats:linkage:TooFewDistances')); + end + passX = true; % the input is the data matrix. + + if ischar(pdistArg) + pdistArg = {pdistArg}; + elseif ~iscell(pdistArg) + error(message('stats:linkage:BadPdistArgs')); + end + +else % should be distance input + passX = false; + if n < 1 + error(message('stats:linkage:TooFewDistances')); + end + if k ~= 1 || m*(m-1)/2 ~= n + error(message('stats:linkage:BadSize')); + end + +end + +% Selects appropriate method +if nargs == 1 % set default switch to be 'si' + methodStr = 'single'; +else + % Preferred Synonym + methods = {'single', 'nearest'; ... + 'complete', 'farthest'; ... + 'average', 'upgma'; ... + 'weighted', 'wpgma'; ... + 'centroid', 'upgmc'; ... + 'median', 'wpgmc'; ... + 'ward''s', 'incremental'}; + [methodStr,s] = internal.stats.getParamVal(method,methods(:),'METHOD'); + + if s>size(methods,1) + methodStr = methods{s-size(methods,1), 1}; + end +end +method = methodStr(1:2); + +% The recursive distance updates for the three methods 'cetroid','median' +% and 'ward' only make sense when the distance matrix contains Euclidean +% distances (which will be squared) or the distance metric is Euclidean +nonEuc = false; +isCeMeWa = false; +if any(strcmp(method,{'ce' 'me' 'wa'})) + isCeMeWa = true; + if ~passX % The distance matrix is passed + if (any(~isfinite(Y)) || ~iseuclidean(Y)) + warning(message('stats:linkage:NotEuclideanMatrix', methodStr)); + end + else % the data matrix is passed + + if (~isempty(pdistArg)) + if (~ischar (pdistArg{1})) + nonEuc = true; + else + % method may be a known name or the name of a user function + distMethods = {'euclidean'; 'minkowski';'mahalanobis'}; + i = find(strncmpi(pdistArg{1}, distMethods, length(pdistArg{1}))); + if length(i) > 1 + error(message('stats:linkage:BadDistance', pdistArg{ 1 })); + elseif (isempty(i) || i == 3 || ... + (i == 2 && length(pdistArg) ~= 1 && isscalar(pdistArg{2}) && pdistArg{2} ~= 2) ) + nonEuc = true; + end + end + + end + if (nonEuc) + warning(message('stats:linkage:NotEuclideanMethod', methodStr)); + end + end +end + +%Parse the memory efficient option 'Savememory' +pnames ={'savememory'}; +dflts = {[]}; + +[memEff] = internal.stats.parseArgs(pnames, dflts, varargin{:}); + +if ~isempty(memEff) + memEff = internal.stats.getParamVal(memEff,{'on'; 'off'},'SaveMemory'); + memEff = strcmp(memEff,'on'); +end + +% The memeory efficient option can be valid only when the following three +% conditions are satisfied: +% * The first input argument is data matrix +% * The linkage is either 'centroid, 'median' or 'ward'. +% * The distance metric is euclidean +if passX && isCeMeWa && ~nonEuc + if isempty(memEff) %Set the default choice for memEff + if n<=20 + memEff = true; + else + memEff = false; + end + + try + tempDistMat=zeros(1,k*(k-1)/2,class(Y)); + catch ME + if (strcmp(ME.identifier,'MATLAB:nomem') || ... + strcmp(ME.identifier,'MATLAB:pmaxsize')); + memEff = true; + else % Otherwise, just let the error propagate. + throw(ME); + end + end + clear tempDistMat; + end +else + if ~isempty(memEff) && memEff + warning(message('stats:linkage:IgnoringMemEff')); + end + memEff = false; +end + +if exist('linkagemex','file')==3 + % call mex file + if passX + if (memEff) + %call the memory efficient algorithm + Z = linkagemex(Y',method,{'euc'},memEff); %note that Y is transposed here + else + Z = linkagemex(Y,method,pdistArg, memEff); + end + else + Z = linkagemex(Y,method); + end + + if any(strcmp(method,{'av' 'wa' 'co' 'we'})) + % if 'ave','ward', 'com' or 'weighted average' is used, we need to + % re-arrange the rows in Z matrix by sorting the third rows of Z matrix. + Z = rearrange(Z); + end +else + warning(message('stats:linkage:NoMexFilePresent')); + if passX + Y = pdist(Y,pdistArg{:}); + end + % optional old linkage function (use if mex file is not present) + Z = linkageold(Y,method); +end + +% Check if the tree is monotonic and warn if not. Z is built so that the rows +% are in non-decreasing height order, thus we can look at the heights in order +% to determine monotonicity, rather than having to explicitly compare each parent +% with its children. +zdiff = diff(Z(:,3)); +if any(zdiff<0) + % With distances that are computed recursively (average, weighted, median, + % centroid, ward's), errors can accumulate. Two nodes that are really + % at the same height in the tree may have had their heights calculated in + % different ways, making them differ by +/- small amounts. Make sure that + % doesn't produce false non-monotonicity warnings. + negLocs = find(zdiff<0); + if any(abs(zdiff(negLocs)) > eps(Z(negLocs,3))) % eps(the larger of the two values) + warning(message('stats:linkage:NonMonotonicTree', methodStr)); + end +end + +% %re-arrange the Z matrix by sorting the third rows of Z matrix. +% function Z2 = rearrange(Z) +% %Get indices to sort Z +% [~,idx] = sort(Z(:,3)); +% +% % Get indices in the reverse direction +% revidx(idx) = 1:length(idx); +% +% % Get vector of desired cluster numbers for Z2 +% nrows=size(Z,1); +% v2 = [1:nrows+1,nrows+1+revidx]; +% +% % Put Z2 into sorted order, without renumbering the clusters +% Z0 = Z(idx,:); +% Z2 = Z0; +% % Renumber the clusters +% Z2(:,1:2) = v2(Z2(:,1:2)); +% +% % Make sure the lower-numbered cluster is in column 1 +% t = Z2(:,1)>Z2(:,2); +% Z2(t,1:2) = Z2(t,[2 1]); + + +%%%%%%%%%%%%%%%%%%%%%%%%% OLD LINKAGE FUNCTION %%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function Z = linkageold(Y, method) +%LINKAGEOLD Create hierarchical cluster tree using only MATLAB code. + +n = size(Y,2); +m = ceil(sqrt(2*n)); % (1+sqrt(1+8*n))/2, but works for large n +if isa(Y,'single') + Z = zeros(m-1,3,'single'); % allocate the output matrix. +else + Z = zeros(m-1,3); % allocate the output matrix. +end + +% during updating clusters, cluster index is constantly changing, R is +% a index vector mapping the original index to the current (row, column) +% index in Y. N denotes how many points are contained in each cluster. +N = zeros(1,2*m-1); +N(1:m) = 1; +n = m; % since m is changing, we need to save m in n. +R = 1:n; + +% Square the distances so updates are easier. The cluster heights will be +% square-rooted back to the original scale after everything is done. +if any(strcmp(method,{'ce' 'me' 'wa'})) + Y = Y .* Y; +end + +for s = 1:(n-1) + if strcmp(method,'av') + p = (m-1):-1:2; + I = zeros(m*(m-1)/2,1); + I(cumsum([1 p])) = 1; + I = cumsum(I); + J = ones(m*(m-1)/2,1); + J(cumsum(p)+1) = 2-p; + J(1)=2; + J = cumsum(J); + W = N(R(I)).*N(R(J)); + [v, k] = min(Y./W); + else + [v, k] = min(Y); + end + + i = floor(m+1/2-sqrt(m^2-m+1/4-2*(k-1))); + j = k - (i-1)*(m-i/2)+i; + + Z(s,:) = [R(i) R(j) v]; % update one more row to the output matrix A + + % Update Y. In order to vectorize the computation, we need to compute + % all the indices corresponding to cluster i and j in Y, denoted by I + % and J. + I1 = 1:(i-1); I2 = (i+1):(j-1); I3 = (j+1):m; % these are temp variables + U = [I1 I2 I3]; + I = [I1.*(m-(I1+1)/2)-m+i i*(m-(i+1)/2)-m+I2 i*(m-(i+1)/2)-m+I3]; + J = [I1.*(m-(I1+1)/2)-m+j I2.*(m-(I2+1)/2)-m+j j*(m-(j+1)/2)-m+I3]; + + switch method + case 'si' % single linkage + Y(I) = min(Y(I),Y(J)); + case 'co' % complete linkage + Y(I) = max(Y(I),Y(J)); + case 'av' % average linkage + Y(I) = Y(I) + Y(J); + case 'we' % weighted average linkage + Y(I) = (Y(I) + Y(J))/2; + case 'ce' % centroid linkage + K = N(R(i))+N(R(j)); + Y(I) = (N(R(i)).*Y(I)+N(R(j)).*Y(J)-(N(R(i)).*N(R(j))*v)./K)./K; + case 'me' % median linkage + Y(I) = (Y(I) + Y(J))/2 - v /4; + case 'wa' % Ward's linkage + Y(I) = ((N(R(U))+N(R(i))).*Y(I) + (N(R(U))+N(R(j))).*Y(J) - ... + N(R(U))*v)./(N(R(i))+N(R(j))+N(R(U))); + end + J = [J i*(m-(i+1)/2)-m+j]; + Y(J) = []; % no need for the cluster information about j. + + % update m, N, R + m = m-1; + N(n+s) = N(R(i)) + N(R(j)); + R(i) = n+s; + R(j:(n-1))=R((j+1):n); +end + +if any(strcmp(method,{'ce' 'me' 'wa'})) + Z(:,3) = sqrt(Z(:,3)); +end + +Z(:,[1 2])=sort(Z(:,[1 2]),2); + + + +function Z = rearrange(Z) + %Get indices to sort Z + [~,idx] = sort(Z(:,3)); + + % Get indices in the reverse direction + revidx(idx) = 1:length(idx); + + % Get vector of desired cluster numbers for Z2 + nrows=size(Z,1); + v2 = [1:nrows+1,nrows+1+revidx]; + + % Put Z2 into sorted order, without renumbering the clusters + Z = Z(idx,:); + + % Renumber the clusters + Z(:,1:2) = v2(Z(:,1:2)); + + % Make sure the lower-numbered cluster is in column 1 + t = Z(:,1)>Z(:,2); + Z(t,1:2) = Z(t,[2 1]); + + % is Z in chronological order? + if any((Z(:,2) >= (1:nrows)'+nrows+1)) + Z = fixnonchronologicalZ(Z); + t = Z(:,1)>Z(:,2); + Z(t,1:2) = Z(t,[2 1]); + end + +function Z = fixnonchronologicalZ(Z) +% Fixes a binary tree that has branches defined in a non-chronological order + +nl = size(Z,1)+1; % number of leaves +nb = size(Z,1); % number of branches +last = nl; % last defined node, we start only with leaves +for i = 1:nb + tn = nl+i; %this node + if any(Z(i,[1,2])>last) + % this node (tn) uses nodes not defined yet, find a node (h) that + % does use nodes already defined so we can interchange them: + h = find(all(Z(i+1:end,[1 2])<=last,2),1)+i; + % change nodes: + Z([i h],:) = Z([h i],:); + nn = nl+h; % new node + % change references to such nodes + %Z([find(Z(1:2*nb) == nn,1) find(Z(1:2*nb) == tn,1)]) = [tn nn]; + %try + tonn=find(Z(1:2*nb) == tn,1); + Z([find(Z(1:2*nb) == nn,1)]) = [tn]; + %catch,keyboard;end + if(~isempty(tonn)) + Z(tonn) = [nn]; + end + end + last = tn; +end + + + diff --git a/linkagemex.mexa64 b/linkagemex.mexa64 new file mode 100755 index 0000000..4275b8b Binary files /dev/null and b/linkagemex.mexa64 differ diff --git a/mat2det.m b/mat2det.m new file mode 100755 index 0000000..a8432e3 --- /dev/null +++ b/mat2det.m @@ -0,0 +1,16 @@ +function res=mat2det(dets); + res.decision=dets(:,5); + res.pos.x1=dets(:,1); + res.pos.x2=dets(:,3); + res.pos.y1=dets(:,2); + res.pos.y2=dets(:,4); + res.imidx=dets(:,7); + res.detector=dets(:,6); + if(size(dets,2)>=8) + res.flip=dets(:,8); + end + if(size(dets,2)>=9) + res.boxid=dets(:,9); + end + res=effstr2str(res); +end diff --git a/myNms.m b/myNms.m new file mode 100755 index 0000000..5bbf86a --- /dev/null +++ b/myNms.m @@ -0,0 +1,56 @@ +function [pick,suppressclust] = myNms(boxes, overlap) +% top = nms_fast(boxes, overlap) +% Non-maximum suppression. (FAST VERSION) +% Greedily select high-scoring detections and skip detections +% that are significantly covered by a previously selected +% detection. +% NOTE: This is adapted from Pedro Felzenszwalb's version (nms.m), +% but an inner loop has been eliminated to significantly speed it +% up in the case of a large number of boxes +% Tomasz Maliseiwicz (tomasz@cmu.edu) + +if isempty(boxes) + pick = []; + return; +end + +x1 = boxes(:,1); +y1 = boxes(:,2); +x2 = boxes(:,3); +y2 = boxes(:,4); +s = boxes(:,end); + +area = (x2-x1+1) .* (y2-y1+1); +[~, I] = sort(s); +if(nargout>=2) + suppressclust=zeros(size(I)); +end + +pick = s*0; +counter = 1; +while ~isempty(I) + + last = length(I); + i = I(last); + pick(counter) = i; + counter = counter + 1; + + xx1 = max(x1(i), x1(I(1:last-1))); + yy1 = max(y1(i), y1(I(1:last-1))); + xx2 = min(x2(i), x2(I(1:last-1))); + yy2 = min(y2(i), y2(I(1:last-1))); + + w = max(0.0, xx2-xx1+1); + h = max(0.0, yy2-yy1+1); + + o = w.*h ./ area(I(1:last-1)); + f=[last; find(o>overlap)]; + if(nargout>=2) + suppressclust(I(f))=counter-1; + end + + I(f) = []; +end + +pick = pick(1:(counter-1)); +% top = boxes(pick,:); diff --git a/myNmsClass.m b/myNmsClass.m new file mode 100755 index 0000000..81506fa --- /dev/null +++ b/myNmsClass.m @@ -0,0 +1,7 @@ +function pick=myNmsClass(dets,overlap) + [dets,~,ord]=distributeby(dets(:,1:5),dets(:,6)); + for(i=1:numel(dets)) + pick{i,1}=ord{i}(myNms(dets{i},overlap)); + end + pick=cell2mat(pick); +end diff --git a/myaddpath.m b/myaddpath.m new file mode 100755 index 0000000..e33b04d --- /dev/null +++ b/myaddpath.m @@ -0,0 +1,5 @@ +addpath('dswork'); +addpath('clipper'); +addpath('hog'); +addpath('util'); +addpath('../libsvm-mat-3.0-1'); diff --git a/overlapReweight.m b/overlapReweight.m new file mode 100755 index 0000000..8774621 --- /dev/null +++ b/overlapReweight.m @@ -0,0 +1,133 @@ +% Re-weight the detections specified by pos such that detections +% overlapping with many other detections receive low weight, and +% detections overlapping with few others receive high weight. +% The output weight for each patch (res2) is computed by (1) breaking +% the image into scale-space voxels, (2) assigning 'ownership' of each +% voxel like in the E-M algorithm, proportional to the weight of every +% patch covering the voxel (in the comments below, we call the +% un-normalized ownership value for a patch the patch's 'claim' to a voxel), +% and (3) summing the ownership values across +% each patch. +function resw=overlapReweight(pos,origw,gridsize,minsize,conf) + try + if(size(pos,1)==0) + resw=[]; + return; + end + pos=(pos-.5)/gridsize+1; + minsize=prod(minsize/gridsize); + imsize=ceil([max(pos(:,4)),max(pos(:,3))]); + % group the detections such that detections with the same label do not + % compete with each other. Hence, in practice ownership for each + % voxel is distributed among different detector groups rather than + % among different detectors. + if(isfield(conf,'groups')) + [pos,origw,ord]=distributeby(pos,origw,(1:numel(origw))',conf.groups); + ord=cell2mat(ord); + else + [pos,origw]=distributeby(pos,origw,(1:numel(origw))'); + ord=(1:numel(origw))'; + end + imsize + dim=[imsize ceil(log2(min(imsize))*5)]; + % field computes the sum of the weights the patches claiming each voxel. + field=zeros(dim); + field2=field; + linidx=1; + addvec={}; + for(j=1:numel(pos)) + allinds={}; + for(i=1:size(pos{j},1)) + p=pos{j}(i,:); + patsize=(p(4)-p(2)+1)*(p(3)-p(1)+1); + lvl=log2(sqrt(patsize/minsize))+1; + p=[p([2 1]) lvl p([4 3]) lvl+5]; + f=floor(p); + b=p-[f(1) f(2) f(3) f(1) f(2) f(3)]; + toadd=getaddvec(b).*origw{j}(i); + if(size(pos{j},1)>1) + % field2 is a temporary set of voxels used to compute one particular group's + % claim to each voxel. + field2(f(1):f(1)+size(toadd,1)-1,f(2):f(2)+size(toadd,2)-1,f(3):f(3)+size(toadd,3)-1)=max(field2(f(1):f(1)+size(toadd,1)-1,f(2):f(2)+size(toadd,2)-1,f(3):f(3)+size(toadd,3)-1),toadd); + allinds{i}=[f(1),f(1)+size(toadd,1)-1,f(2),f(2)+size(toadd,2)-1,f(3), f(3)+size(toadd,3)-1];%inds; + else + try + field(f(1):f(1)+size(toadd,1)-1,f(2):f(2)+size(toadd,2)-1,f(3):f(3)+size(toadd,3)-1)=field(f(1):f(1)+size(toadd,1)-1,f(2):f(2)+size(toadd,2)-1,f(3):f(3)+size(toadd,3)-1)+toadd; + catch ex,dsprinterr;end + linidx=linidx+1; + end + end + if(size(pos{j},1)>1) + field=field+field2; + for(i=1:size(pos{j},1)) + f=allinds{i}; + % For each patch, keep track of the group's claim within the area of that patch. + addvec{linidx}=c(field2(f(1):f(2),f(3):f(4),f(5):f(6)))./max(origw{j}(i),eps);%(fieldtmp(allinds{i})./max(origw{j}(i),eps)); + linidx=linidx+1; + end + field2(:)=0; + end + end + % compute the inverse of the claims, since we compute the weight of a given patch as the sum over voxels + % of a patch's claim to that voxel divided by the total claim to that voxel. + field=1./max(field,eps);%bsxfun(@max,field,reshape(1/(prod(minsize)*2.^(0:size(field,3)-1),1,1,[]))); + + pos=cell2mat(pos); + origw=cell2mat(origw); + resw=zeros(size(origw)); + % for each patch, re-compute its claim to each patch and multiply by + % the values in field. + for(i=1:size(pos,1)) + p=pos(i,:); + patsize=(p(4)-p(2)+1)*(p(3)-p(1)+1); + lvl=log2(sqrt(patsize/minsize))+1; + p=[p([2 1]) lvl p([4 3]) lvl+5]; + f=floor(p); + b=p-[f(1) f(2) f(3) f(1) f(2) f(3)]; + toadd=getaddvec(b); + sta=sum(toadd(:)); + if(numel(addvec)>=i && ~isempty(addvec{i})) + toadd=reshape(addvec{i},size(toadd)); + end + if(sta>0) + toadd=toadd./sta; + end + try + resw(i)=resw(i)+(reshape(field(f(1):f(1)+size(toadd,1)-1,f(2):f(2)+size(toadd,2)-1,f(3):f(3)+size(toadd,3)-1),1,[])*toadd(:))*origw(i); + catch ex,dsprinterr;end + %end + end + if(any(resw>1.000001)),try,error('weightincrease');catch ex, dsprinterr;end,end + if(any(isnan(resw))),try,error('nan');catch ex, dsprinterr;end,end + resw=(min(resw,1).*origw);% + resw(ord)=resw; + if(any(resw<0)),try,error('less than zero');catch ex, dsprinterr;end,end + catch ex,dsprinterr;end +end +%get the list of scale-space voxels for a particular detection, after it's been +%re-scaled into voxel coordinates. +function toadd=getaddvec(b) + toadd=zeros(ceil(b(3:4))); + dim=ceil(b(4:6)); + permvec=[]; + for(i=1:3) + interpv=ones(dim(i),1); + %interpv(1)=1-b(i); + %interpv(end)=b(i+3)-numel(interpv)+1; + permvec=[i permvec]; + if(i>1) + interpv=permute(interpv,permvec); + end + mult{i}=interpv; + end + toadd=bsxfun(@times,bsxfun(@times,mult{1},mult{3}),mult{2}); + %toadd(2:end-1,2:end-1)=1; + %toadd(1,2:end-1)=1-b(1); + %toadd(end,2:end-1)=b(3)-size(toadd,2)+1; + %toadd(2:end-1,1)=1-b(2); + %toadd(2:end-1,end)=b(4)-size(toadd,2)+1; + %toadd(1,1)=toadd(1,2)*toadd(2,1); + %toadd(1,end)=toadd(2,end)*toadd(1,end-1); + %toadd(end,1)=toadd(end,2)*toadd(end-1,1); + %toadd(end,end)=toadd(end-1,end)*toadd(end,end-1); +end diff --git a/overlapReweightForImg.m b/overlapReweightForImg.m new file mode 100755 index 0000000..ff83c66 --- /dev/null +++ b/overlapReweightForImg.m @@ -0,0 +1,45 @@ +% compute the alpha values for a single image. dets is just a list +% of detections in standard form [x1 y1 x2 y2 score detrid imageid flip] +function reweights=overlapReweightForImg(dets,detridclassgroup) +global ds; +[~,idx]=ismember(dets(:,6),detridclassgroup(:,1)); +imgs=dsload('ds.imgs{ds.conf.currimset}'); +toovlweight1=detridclassgroup(idx,2)==imgs.label(dets(:,7)); +if(~dsbool(ds.conf.params,'ovlweight')) + reweights=ones(size(dets(:,1))); +else + toovlweightf=find(toovlweight1); + % break the detections up into flipped and un-flipped. + if(size(dets,2)>=8) + toovlweightall{1}=toovlweightf(dets(toovlweightf,8)==0); + toovlweightall{2}=toovlweightf(dets(toovlweightf,8)==1); + else + toovlweightall={toovlweightf}; + end + reweights=ones(size(idx)); + + for(i=1:numel(toovlweightall)) + toovlweight=toovlweightall{i}; + if(numel(toovlweight)>0) + conf.groups=detridclassgroup(idx,3); + conf.groups=conf.groups(toovlweight); + % we set the initial weights of each detection to the detection score; + % these weights in effect determine how strongly each detection "competes" + % for pixels. + % Detections with a negative score get a small epsilon value, + % because we're computing the re-weigting based on the inverses of + % these scores. Hence negative values make no sense. + % The output of overlapReweight is scaled by the input weight (overlapReqeight RE-weights + % it doesn't know that we made up the input weights). + reweights(toovlweight)=overlapReweight(dets(toovlweight,1:4),max(0,dets(toovlweight,5))+.000001,ds.conf.params.sBins,ds.conf.params.patchCanonicalSize,conf)./(max(0,dets(toovlweight,5))+.000001); + if(any(reweights>1e10)),error('fail');end + end + end +end +% finally, scale the weights by the number of detections for each +% detector. +udetr=unique(idx); +idx(~toovlweight1)=0; +for(i=1:numel(udetr)) + reweights(idx==udetr(i))=reweights(idx==udetr(i))./sum(idx==udetr(i)); +end diff --git a/patch2feat.m b/patch2feat.m new file mode 100755 index 0000000..bf1b196 --- /dev/null +++ b/patch2feat.m @@ -0,0 +1,18 @@ +function res=patch2feat(trpatches,conf) + global ds; + extrparams=ds.conf.params; + extrparams.imageCanonicalSize=[max(min(size(trpatches{1}(:,:,1))),min(extrparams.patchCanonicalSize))]; + extrparams.basePatchSize=size(trpatches{1}(:,:,1)); + tmp=constructFeaturePyramidForImg(im2double(trpatches{1}),extrparams,1); + %res(1,:)=tmp.features{1}(:)'; + pcs=round(ds.conf.params.patchCanonicalSize/ds.conf.params.sBins)-2; + [res(1,:), levels, indexes,gradsums] = unentanglePyramid(tmp, ... + pcs,conf); + res(2:numel(trpatches),:)=0;%=zeros(numel(trpatches),numel(tmp.features{1})); + for(i=2:numel(trpatches)) + tmp=constructFeaturePyramidForImg(im2double(trpatches{i}),extrparams,1); + %res(i,:)=tmp.features{1}(:)'; + [res(i,:), levels, indexes,gradsums] = unentanglePyramid(tmp, ... + pcs,conf); + end +end diff --git a/patchdisplay.m b/patchdisplay.m new file mode 100755 index 0000000..e47b88f --- /dev/null +++ b/patchdisplay.m @@ -0,0 +1,82 @@ +ds_html{end+1}=sprintf('\n'); +ds_html{end+1}=sprintf(''); +if(isfield(argv,'mainimgs')) +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' '); +for(i=1:numel(argv.mainimgs)) +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' '); +end +ds_html{end+1}=sprintf('
    \n'); +ds_html{end+1}=sprintf(''); +end +ds_html{end+1}=sprintf('\n'); +ds_html{end+1}=sprintf(''); + +if(~isfield(argv,'ovlweights')) + argv.ovlweights=zeros(size(argv.dets(:,[]))); +end +[dets,posinpatchimg,ovlweights,detid]=distributeby(argv.dets,(1:size(argv.dets,1))',argv.ovlweights,argv.dets(:,6)); +if(isfield(argv,'detrord')) + [~,idxord]=ismember(argv.detrord,detid); +else + idxord=1:numel(detid); + argv.detrord=detid; +end +if(~isfield(argv,'message')) + argv.message=repmat({''},numel(argv.detrord),1); +end +gbz=dsload('ds.conf.gbz{ds.conf.currimset}'); +imgs=dsload('.ds.imgs{ds.conf.currimset}'); +if(~isfield(gbz,'imgsurl')) + gbz.imgsurl=''; +end +for(i=1:numel(idxord)) +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' '); +if(idxord(i) ~= 0) + curdets=dets{idxord(i)}; + curpos=posinpatchimg{idxord(i)}; + curwt=ovlweights{idxord(i)}; + [~,ord]=sort(curdets(:,5),'descend'); + for(j=1:size(curdets,1)) +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' '); +end + end +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(''); +end +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf('
    detector no. '); +ds_html{end+1}=num2str([argv.detrord(i)]); +ds_html{end+1}=sprintf('
    '); +ds_html{end+1}=num2str([argv.message{i}]); +ds_html{end+1}=sprintf('
    \n'); +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf(' \n'); +ds_html{end+1}=sprintf('
    \n'); +ds_html{end+1}=sprintf('\n'); +ds_reshtml=cell2mat(ds_html); +ds_html=[]; diff --git a/patchdisplay.mhp b/patchdisplay.mhp new file mode 100755 index 0000000..3312f25 --- /dev/null +++ b/patchdisplay.mhp @@ -0,0 +1,48 @@ + +<%if(isfield(argv,'mainimgs'))%> + + <%for(i=1:numel(argv.mainimgs))%> + + <%end%> +
    +<%end%> + +<% +if(~isfield(argv,'ovlweights')) + argv.ovlweights=zeros(size(argv.dets(:,[]))); +end +[dets,posinpatchimg,ovlweights,detid]=distributeby(argv.dets,(1:size(argv.dets,1))',argv.ovlweights,argv.dets(:,6)); +if(isfield(argv,'detrord')) + [~,idxord]=ismember(argv.detrord,detid); +else + idxord=1:numel(detid); + argv.detrord=detid; +end +if(~isfield(argv,'message')) + argv.message=repmat({''},numel(argv.detrord),1); +end +gbz=dsload('ds.conf.gbz{ds.conf.currimset}'); +imgs=dsload('.ds.imgs{ds.conf.currimset}'); +if(~isfield(gbz,'imgsurl')) + gbz.imgsurl=''; +end +for(i=1:numel(idxord)) %> + + <%if(idxord(i) ~= 0) + curdets=dets{idxord(i)}; + curpos=posinpatchimg{idxord(i)}; + curwt=ovlweights{idxord(i)}; + [~,ord]=sort(curdets(:,5),'descend'); + for(j=1:size(curdets,1)) %> + + <%end + end%> + +<%end%> + +
    detector no. <%=argv.detrord(i)%>
    <%=argv.message{i}%>
    + + + +
    + diff --git a/preprocessindoor67.m b/preprocessindoor67.m new file mode 100755 index 0000000..7b26e8f --- /dev/null +++ b/preprocessindoor67.m @@ -0,0 +1,67 @@ +% Author: Carl Doersch (cdoersch at cs dot cmu dot edu) +% +% Generate an imgs structure and save it to gbz.datasetname. +% It reads all directories contained in gbz.cutoutdir and +% collects all of the images. +function preprocessindoor67(cutoutdir,datasetfiles) +for(imset=1:2) + imdirs=dir(cutoutdir); + [~,inds]=sort({imdirs.name}) + imdirs=imdirs(inds); + imgs=[]; + imdata=[]; + smdata=[]; + if(imset==1) + mitimgs=textread('TrainImages.txt','%s','delimiter','\n'); + else + mitimgs=textread('TestImages.txt','%s','delimiter','\n'); + end + for(fn=1:numel(imdirs)) + if(strcmp(imdirs(fn).name,'.')||strcmp(imdirs(fn).name,'..')) + continue; + end + imdirs(fn).name + imgs1=cleandir([cutoutdir '/' imdirs(fn).name]); + [~,inds]=sort({imgs1.name}); + imgs1=imgs1(inds); + rand('seed',fn); + s=randperm(numel(imgs1)); + imgs2={}; + for(m=1:numel(imgs1)) + if(~ismember(lower([imdirs(fn).name filesep imgs1(m).name]),lower(mitimgs))) + continue; + end + [~,pos]=ismember(lower([imdirs(fn).name filesep imgs1(m).name]),lower(mitimgs)); + mitimgs(pos)=[]; + if(dshassuffix(imgs1(m).name,'_gif.jpg')) + fnm=[cutoutdir '/' imdirs(fn).name filesep imgs1(m).name]; + [im,map]=imread(fnm); + if(size(map,2)>0) + for(i=1:size(map,2)) + channel=map(:,i); + data{i}=channel(im+1); + end + im=cat(3,data{:}); + end + clear data + fnm(end-6:end)='jpg.jpg'; + imgs1(m).name(end-6:end)='jpg.jpg'; + imwrite(im,fnm,'Quality',85); + imgs1(m)=dir(fnm); + end + + imgs2{end+1}.fullname=[imdirs(fn).name filesep imgs1(m).name]; + imtmp=imread([cutoutdir imgs2{end}.fullname]); + sz=size(imtmp); + imgs2{end}.imsize=sz(1:2); + imgs2{end}.label=imdirs(fn).name; + end + imgs=[imgs;cell2mat(imgs2')]; + %imdata=[imdata; imdata1]; + %smdata=[smdata;smdata1]; + end + imgs=str2effstr(imgs); + labelnames=sort(unique(imgs.label)); + [~,imgs.label]=ismember(imgs.label,labelnames); + save(datasetname{i},'imgs','labelnames'); +end diff --git a/pyridx2pos.m b/pyridx2pos.m new file mode 100755 index 0000000..ef03153 --- /dev/null +++ b/pyridx2pos.m @@ -0,0 +1,35 @@ +% Author: Carl Doersch (cdoersch at cs dot cmu dot edu) +% a relatively efficient function to convert a HOG pyramid index +% (i.e. pyramid level and grid cell) into a position +% in pixel space. +function metadata=pyridx2pos(idx,pyrsc,patchcellsize,pyramid)%pyrcanosz,pyrsc,prSize,pcSize,sBins,imsize); +pyrcanosz=pyramid.canonicalScale; +sBins=pyramid.sbins; +imsize=[pyramid.canonicalSize.nrows pyramid.canonicalSize.ncols]; +prSize=patchcellsize(1); +pcSize=patchcellsize(2); +levSc = pyramid.scales(pyrsc); +levSc=levSc(:); +canoSc = pyrcanosz; +% [x1 x2 y1 y2] +levelPatch = [ ... + zeros(size(levSc)), ... + round((prSize + 2) .* sBins .* levSc ./ canoSc) - 1, ... + zeros(size(levSc)), ... + round((pcSize + 2) .* sBins .* levSc ./ canoSc) - 1, ... +]; +x1=idx(:,2); +y1=idx(:,1); +xoffset = floor((x1 - 1) .* sBins .* levSc / canoSc) + 1; + yoffset = floor((y1 - 1) .* sBins .* levSc / canoSc) + 1; + thisPatch = bsxfun(@plus,levelPatch, [xoffset xoffset yoffset yoffset]); + + metadata.x1 = max(1,thisPatch(:,1)); + metadata.x2 = min(thisPatch(:,2),imsize(2)); + metadata.y1 = max(1,thisPatch(:,3)); + metadata.y2 = min(thisPatch(:,4),imsize(1)); +%if(metadata.x2>936) +%keyboard +%end +end + diff --git a/sampleRandomPatchesbb.m b/sampleRandomPatchesbb.m new file mode 100755 index 0000000..a385ee2 --- /dev/null +++ b/sampleRandomPatchesbb.m @@ -0,0 +1,171 @@ +%v1 by Saurabh Singh +%Edit: Carl Doersch (cdoersch at cs dot cmu dot edu). +function [patches, patFeats, probabilities] = sampleRandomPatches(pos,samplelimit,conf) +if(~exist('conf','var')) + conf=struct(); +end +global ds; +params=ds.conf.params; +%data = pos; +%pos = pos.annotation; +rand('seed',1000*pos); +I2 = im2double(getimg(ds,pos));%imread([ds.conf.gbz{ds.conf.currimset}.cutoutdir ds.imgs(pos).fullpath])); +if(dsfield(conf,'detsforclass')) + annot=getannot(pos); + bbs=[annot.x1 annot.y1 annot.x2 annot.y2]; + classes=[annot.label]; + occl=annot.occluded; + difficult=annot.difficult; + boxid=annot.boxid; + bbminsize=[bbs(:,4)-bbs(:,2)+1,bbs(:,3)-bbs(:,1)+1]; + valid=(~occl & c(ismember(classes,conf.detsforclass)) & ~difficult & all(bsxfun(@ge,bbminsize,ds.conf.params.patchCanonicalSize*1.5),2)); + bbs(~valid,:)=[]; + boxid(~valid)=[]; + bbs=[bbs,repmat([-Inf,-Inf,pos,0],size(bbs,1),1),boxid]; +else + bbs=[1,1,size(I2,2),size(I2,1),-Inf,-Inf,pos,0,1]; +end +patchesall={}; +patFeatsall={} +probabilitiesall={} +for(bbidx=1:size(bbs,1)) + I=I2(bbs(bbidx,2):bbs(bbidx,4),bbs(bbidx,1):bbs(bbidx,3),:); + if(dsfield(params,'imageCanonicalSize')) + [IS, scale] = convertToCanonicalSize(I, params.imageCanonicalSize); + else + IS=I; + scale=1; + end + [rows, cols, unused] = size(IS); + IG = getGradientImage(IS); + pyramid = constructFeaturePyramidForImg(I, params); + conf.imid=pos; + pcs=round(ds.conf.params.patchCanonicalSize/ds.conf.params.sBins)-2; + [features, levels, indexes] = unentanglePyramid(pyramid, ... + pcs,conf); + %params.patchCanonicalSize); + selLevels = 1 : params.scaleIntervals/2 : length(pyramid.scales); + levelScales = pyramid.scales(selLevels); + numLevels = length(selLevels); + + patches = []; + patFeats = []; + probabilities = []; + basenperlev=pyramid.features{selLevels(end)}; + basenperlev=(basenperlev(1)-pcs(1)+1)*(basenperlev(2)-pcs(2)+1); + for i = 1 : numLevels + levPatSize = floor(params.patchCanonicalSize .* levelScales(i)); + if(dsbool(params,'sampleBig')) + numLevPat=floor(basenperlev/2); + else + numLevPat = floor((rows / levPatSize(1)) * ... + (cols / levPatSize(2))*8); + end + %disp([num2str(levelScales(i)) '->' num2str(numLevPat)]); + + levelPatInds = find(levels == selLevels(i)); + if numLevPat <= 0 + continue; + end + + IGS = IG; + pDist = getProbDistribution(IGS, levPatSize); + pDist1d = pDist(:); + randNums = getRandForPdf(pDist1d, numLevPat); + probs = pDist1d(randNums); + [IY, IX] = ind2sub(size(IGS), randNums); + IY = ceil(IY ./ (levelScales(i) * params.sBins)); + IX = ceil(IX ./ (levelScales(i) * params.sBins)); + + [nrows, ncols, unused] = size(pyramid.features{selLevels(i)}); + IY = IY - floor(pcs(1) / 2); + IX = IX - floor(pcs(2) / 2); + xyToSel = IY>0 & IY<=nrows-pcs(1)+1 & IX>0 & IX<=ncols-pcs(2)+1; + IY = IY(xyToSel); + IX = IX(xyToSel); + probs = probs(xyToSel); + inds = sub2ind([nrows-pcs(1)+1 ncols-pcs(2)+1], IY, IX); + [inds, m, unused] = unique(inds); + probs = probs(m); + selectedPatInds = levelPatInds(inds,:); + fsz=(ds.conf.params.patchCanonicalSize-2*ds.conf.params.sBins)/ds.conf.params.sBins; + metadata = pyridx2pos(indexes(selectedPatInds,:),levels(selectedPatInds),fsz,pyramid) + %getMetadataForPositives(selectedPatInds, levels,... + % indexes, pcs(1), pcs(2), pyramid, pos); + feats = features(selectedPatInds, :); + if ~isempty(metadata) + patInds = cleanUpOverlappingPatches(metadata, ... + params.samplingOverlapThreshold); + metadata.x1=metadata.x1+bbs(bbidx,1)-1; + metadata.x2=metadata.x2+bbs(bbidx,1)-1; + metadata.y1=metadata.y1+bbs(bbidx,2)-1; + metadata.y2=metadata.y2+bbs(bbidx,2)-1; + if(any(metadata.y21) + if(~exist('weburl','var')) + weburl=''; + end + gbz=struct('labelnames',{labelnames},'imgsurl',weburl,'cutoutdir',datadir); + mydir=dspwd; + dscd('.ds'); + dsup(['ds.imgs{' num2str(idx) '}'],imgs); + dsup(['ds.conf.gbz{' num2str(idx) '}'],gbz); + dscd(mydir); + dsup(['ds.conf.gbz{' num2str(idx) '}'],gbz);% TODO: this should really only be kept in + % the root .ds.conf; relying on the + % non-root ds.conf creates + % unintuitive behavior. + end +end diff --git a/unentanglePyramid.m b/unentanglePyramid.m new file mode 100755 index 0000000..68a176b --- /dev/null +++ b/unentanglePyramid.m @@ -0,0 +1,112 @@ +function [features, levels, indexes, gradsums] = unentanglePyramid(pyramid, ... + pcsz,conf) + if(~exist('conf','var')) + conf=struct(); + end + global ds; + conf=overrideConf(ds.conf.params,conf); +% Converts a pyramid of hog features for an image to a single matrix. +% +% Author: saurabh.me@gmail.com (Saurabh Singh). + + %prSize = round(patchCanonicalSize(1) / pyramid.sbins) - 2; + %pcSize = round(patchCanonicalSize(2) / pyramid.sbins) - 2; + %[prSize, pcSize, pzSize, nExtra]=getCanonicalPatchHOGSize(ds.conf.params); + %prSize=pc + selFeatures = cell(length(pyramid.features), 1); + selGradsums = cell(length(pyramid.features), 1); + selFeaturesInds = cell(length(pyramid.features), 1); + + selLevel = cell(length(pyramid.features), 1); + totalProcessed = 0; + if(numel(pyramid.features)==0) + features=[]; + levels=[]; + indexes=[]; + gradsums=[]; + return + end + %im=RGB2Lab(im); + if(numel(pcsz)<3) + pcsz(3)=size(pyramid.features{1},3); + end + if(numel(pcsz)<4) + pcsz(4)=0; + end + + for i = 1 : length(pyramid.features) + [feats, indexes, selGradsums{i,1}] = getFeaturesForLevel(pyramid.features{i}, conf, pcsz(1), ... + pcsz(2),pcsz(3),pcsz(4),pyramid.gradimg{i}); + selFeatures{i} = feats; + selFeaturesInds{i} = indexes; + numFeats = size(feats, 1); + selLevel{i} = ones(numFeats, 1) * i; + totalProcessed = totalProcessed + numFeats; + end + gradsums=cell2mat(selGradsums); + + [features, levels, indexes] = appendAllTogether(totalProcessed, ... + selFeatures, selLevel, selFeaturesInds); + 'featsize' + size(feats) + clear selFeatures; + if(dsbool(conf,'normbeforewhit')) + features=bsxfun(@rdivide,bsxfun(@minus,features,mean(features,2)),max(sqrt(var(features,1,2).*size(features,2)),.0000001)); + end + if(dsbool(conf,'whitening')) + try + whit=dsload('.ds.whitenmat'); + whiten=1; + catch + whiten=0; + end + if(whiten) + features=bsxfun(@minus,features,(dsload('.ds.datamean')))*(dsload('.ds.whitenmat')'); + end + end + if(dsbool(conf,'normalizefeats')) + features=bsxfun(@rdivide,bsxfun(@minus,features,mean(features,2)),sqrt(var(features,1,2).*size(features,2))); + end + if(dsfield(conf,'contextfeats')) + disp('contextfeats'); + dets=getImgDetections(conf.imid); + if(~isempty(dets)) + dets=dets(ismember(dets(:,6),conf.contextfeats),:); + end + patsz=ds.conf.params.patchCanonicalSize;%allsz(resinds(k),:); + fsz=(patsz-2*ds.conf.params.sBins)/ds.conf.params.sBins; + %sz=size(ds.centers{c}); + %sz=sz(1:2); + %idxpad=floor((sz-fsz)./2); + imgs=getimgs(); + %pos=pyridx2pos(indexes,pyramid.canonicalScale,reshape(levels,[],1),... + % fsz(1),fsz(2),pyramid.sbins,[pyramid.canonicalSize.nrows pyramid.canonicalSize.ncols]); + pos=pyridx2pos(indexes,reshape(levels,[],1),fsz,pyramid); + pos=[pos.x1 pos.y1 pos.x2 pos.y2]; + features=[features contextfeats(pos,dets,conf.contextfeats)]; + end + +end + +function [newFeat, newLev, newInds] = appendAllTogether(totalProcessed, ... + features, levels, indexes) +newFeat = zeros(totalProcessed, size(features{1}, 2)); +newLev = zeros(totalProcessed, 1); +newInds = zeros(totalProcessed, 2); + +featInd = 1; +for i = 1 : length(features) + if isempty(features{i}) + continue; + end + startInd = featInd; + endInd = startInd + size(features{i}, 1) - 1; + newFeat(startInd:endInd, :) = features{i}; + features{i} = []; + newLev(startInd:endInd) = levels{i}; + levels{i} = []; + newInds(startInd:endInd, :) = indexes{i}; + indexes{i} = []; + featInd = endInd + 1; +end +end diff --git a/util/c.m b/util/c.m new file mode 100755 index 0000000..201bc90 --- /dev/null +++ b/util/c.m @@ -0,0 +1,3 @@ +function a=c(a) +a=a(:); +end diff --git a/util/distributeby.m b/util/distributeby.m new file mode 100755 index 0000000..781a82d --- /dev/null +++ b/util/distributeby.m @@ -0,0 +1,61 @@ +% [Aout, ..., uniquebins, inverse]=distributeby(A, ..., bins) will +% distribute A into a cell array, where rows of A that have the same +% values in the rows of bins will be mapped to the same cell of A. +% +% Specifically, uniquebins is the output of unique(bins,'rows'), where the +% rows are sorted, first by the first column, then by the second column, +% and so forth. For any index i, Aout{i} will be the (order-preserving) +% concatenation of all A(j,:) where all(bins(j,:)==uniquebins(i,:)). If A is +% a struct, it is assumed to be a set of parallel arrays (i.e. an effstr); each +% A{i} will be a struct with the same fields as the input, but with each +% field containing only the values matching a particular value in bins. +% Finally, inverse is a cell array the same size as Aout, where inverse{i}(j) +% specifies which row of A corresponds to Aout{i}(j,:). Note that +% invertdistributeby(Aout,inverse)=A. +% +% Additional arguments after A will have the exact same operation performed +% on them as A. +% +% [...]=distributeby(...,'descend') will invert the ordering of the +% uniquebins output, and all the other variables as well. +function [varargout]=distributeby(varargin) + %order='ascend'; + order=1; + if(ischar(varargin{end})&&strcmp('descend',varargin{end})) + %order='descend'; + order=-1; + varargin(end)=[]; + end + if(ischar(varargin{end})&&strcmp('ascend',varargin{end})) + varargin(end)=[]; + end + bins=varargin{end}; + mat=varargin(1:end-1); + binid=sortrows(unique(bins,'rows'),(1:size(bins,2))*order); + [~,idx]=ismember(bins,binid,'rows'); + [~,ord]=sort(idx); + counts=histc(idx,1:size(binid,1)); + for(i=1:numel(mat)) + if(isstruct(mat{i})) + fns=fieldnames(mat{i}); + restmp=cell(numel(counts),numel(fns)); + for(j=1:numel(fns)) + field=mat{i}.(fns{j}); + field=field(ord,:); + restmp(:,j)=mat2cell(field,counts,size(field,2)); + end + restmp=cell2struct(restmp,fns,2); + varargout{i}=num2cell(restmp); + %restmp=mat2cell(restmp,ones(numel(counts),1),size(restmp,2)); + %fn=fieldnames(mat{i}); + %restmp=cellfun(@(x) cell2struct( + else + res=mat{i}(ord,:); + varargout{i}=mat2cell(res,counts,size(res,2)); + end + end + varargout{end+1}=binid; + if(nargout>numel(varargout)) + varargout{end+1}=mat2cell(ord(:),counts,1); + end +end diff --git a/util/effstr2str.m b/util/effstr2str.m new file mode 100755 index 0000000..1eb4cae --- /dev/null +++ b/util/effstr2str.m @@ -0,0 +1,53 @@ +function res=effstr2str(str,ressize) + if(nargin<2) + ressize=getressize(str) + end + if(~isstruct(str)) + if(size(str,1)==1) + res=repmat(str,ressize,1); + elseif(size(str,1)==0) + res=zeros(ressize,0); + else + res=str; + end + else + res=struct(); + fnms=fieldnames(str); + for(i=1:numel(fnms)) + f=getfield(str,fnms{i}); + if(size(f,1)>1) + ressize=size(f,1); + break; + end + end + for(i=1:numel(fnms)) + f=effstr2str(getfield(str,fnms{i}),ressize); + try + t=mat2cell(f,ones(1,ressize),size(f,2)); + catch,keyboard;end + if(~exist('fval','var')) + fval=cell(numel(t),numel(fnms)); + end + fval(:,i)=t; + end + res=cell2struct(fval,fnms,2); + end +end +function ressize=getressize(str) + if(~isstruct(str)) + if(size(str,1)~=1) + ressize=size(str,1); + else + ressize=1; + end + return; + end + fnms=fieldnames(str); + for(i=1:numel(fnms)) + f=getfield(str,fnms{i}); + ressize=getressize(f); + if(ressize~=1) + return; + end + end +end diff --git a/util/effstrcell2mat.m b/util/effstrcell2mat.m new file mode 100755 index 0000000..6b01055 --- /dev/null +++ b/util/effstrcell2mat.m @@ -0,0 +1,4 @@ +function res=effstrcell2mat(data) + data=cell2mat(data); + res=str2effstr(data); +end diff --git a/util/effstridx.m b/util/effstridx.m new file mode 100755 index 0000000..fa0c7ee --- /dev/null +++ b/util/effstridx.m @@ -0,0 +1,22 @@ +function res=effstridx(str,idx,expand) + if(nargin<3) + expand=false; + end + if(~isstruct(str)) + if(numel(str)==1) + if(expand) + res=repmat(str,numel(idx),1); + else + res=str; + end + else + res=str(idx,:); + end + else + res=struct(); + fnms=fieldnames(str); + for(i=1:numel(fnms)) + res=setfield(res,fnms{i},effstridx(getfield(str,fnms{i}),idx,expand)); + end + end +end diff --git a/util/idxof.m b/util/idxof.m new file mode 100644 index 0000000..deecd28 --- /dev/null +++ b/util/idxof.m @@ -0,0 +1,3 @@ +function res=idxof(varargin) + [~,res]=ismember(varargin{:}); +end diff --git a/util/idxwithdefault.m b/util/idxwithdefault.m new file mode 100755 index 0000000..a624eb3 --- /dev/null +++ b/util/idxwithdefault.m @@ -0,0 +1,10 @@ +function res=idxwithdefault(data,inds,default) + iscols=size(data,1)==0; + data=[default;data(:)]; + inds(inds<=0)=0; + inds=inds+1; + res=data(inds); + if(iscols) + res=data'; + end +end diff --git a/util/invertdistributeby.m b/util/invertdistributeby.m new file mode 100755 index 0000000..7d4da9a --- /dev/null +++ b/util/invertdistributeby.m @@ -0,0 +1,9 @@ +% invert a distributeby operation, i.e. if you compute +% [Aout,~,inverse]=distributeby(A,bins) +% You can compute B as some function on each value in each cell of Aout +% and then call invertedistributeby(B,inverse) to get an array +% that's parallel with A again. +function val=invertdistributeby(val,ord) + val=cell2mat(val); + val(cell2mat(ord),:)=val; +end diff --git a/util/maxk.m b/util/maxk.m new file mode 100755 index 0000000..e17f091 --- /dev/null +++ b/util/maxk.m @@ -0,0 +1,5 @@ +function [val,pos]=maxk(x,k) + [val,pos]=sort(x,'descend'); + val=val(1:min(numel(x),k)); + pos=pos(1:min(numel(x),k)); +end diff --git a/util/maxkall.m b/util/maxkall.m new file mode 100755 index 0000000..e073268 --- /dev/null +++ b/util/maxkall.m @@ -0,0 +1,8 @@ +function data=maxkall(data,idx,nmax) +try + for(i=1:numel(data)) + [~,ord]=maxk(data{i}(:,idx),nmax); + data{i}=data{i}(ord,:); + end +catch ex,dsprinterr;end +end diff --git a/util/mink.m b/util/mink.m new file mode 100755 index 0000000..d238a71 --- /dev/null +++ b/util/mink.m @@ -0,0 +1,5 @@ +function [val,pos]=maxk(x,k) + [val,pos]=sort(x); + val=val(1:min(numel(x),k)); + pos=pos(1:min(numel(x),k)); +end diff --git a/util/minkall.m b/util/minkall.m new file mode 100755 index 0000000..d349541 --- /dev/null +++ b/util/minkall.m @@ -0,0 +1,8 @@ +function data=maxkall(data,idx,nmax) +try + for(i=1:numel(data)) + [~,ord]=mink(data{i}(:,idx),nmax); + data{i}=data{i}(ord,:); + end +catch ex,dsprinterr;end +end diff --git a/util/mymemory.m b/util/mymemory.m new file mode 100755 index 0000000..e7b8d84 --- /dev/null +++ b/util/mymemory.m @@ -0,0 +1,11 @@ +% Author: Carl Doersch (cdoersch at cs dot cmu dot edu) +% +% Print an estimate of memory usage for the current workspace. +%function res=mymemory +%ans=[]; +memory_a=whos; +%if(nargout==1) +%else + disp(['workspace memory usage: ' num2str(sum([memory_a.bytes])) ' bytes']); + mem_total=num2str(sum([memory_a.bytes])); +%end diff --git a/util/normrows.m b/util/normrows.m new file mode 100755 index 0000000..ccd178c --- /dev/null +++ b/util/normrows.m @@ -0,0 +1,14 @@ +function res=normrows(X,meansubtract,thresh) + if(nargin>1&&meansubtract) + X=bsxfun(@minus,X,mean(X,2)); + end + if(nargin<3) + thresh=0; + end + nrm=sqrt(sum(X.^2,2)); + badinds=nrm<=thresh; + res=bsxfun(@rdivide,X,nrm); + if(nargin>2) + res(badinds,:)=0; + end +end diff --git a/util/overrideConf.m b/util/overrideConf.m new file mode 100755 index 0000000..a1bc832 --- /dev/null +++ b/util/overrideConf.m @@ -0,0 +1,10 @@ +function baseconf=overrideconf(baseconf,newconf) + fnms=fieldnames(newconf) + for(i=1:numel(fnms)) + if(isfield(baseconf,fnms{i})&&isstruct(getfield(baseconf,fnms{i}))&&isstruct(getfield(newconf,fnms{i}))) + baseconf=setfield(baseconf,fnms{i},overrideConf(getfield(baseconf,fnms{i}),getfield(newconf,fnms{i}))); + else + baseconf=setfield(baseconf,fnms{i},getfield(newconf,fnms{i})); + end + end +end diff --git a/util/str2effstr.m b/util/str2effstr.m new file mode 100755 index 0000000..987d715 --- /dev/null +++ b/util/str2effstr.m @@ -0,0 +1,15 @@ +function res=str2effstr(str) + nams=fieldnames(str); + res=struct(); + for(i=1:numel(nams)) + if(numel(str)>0&&ischar(getfield(str(1),nams{i}))) + restruct={str.(nams{i})}'; + else + restruct=cat(1,str.(nams{i})); + if(isstruct(restruct)) + restruct=str2effstr(str); + end + end + res=setfield(res,nams{i},restruct); + end +end diff --git a/util/structcell2mat.m b/util/structcell2mat.m new file mode 100755 index 0000000..bcc909f --- /dev/null +++ b/util/structcell2mat.m @@ -0,0 +1,14 @@ +% Author: Carl Doersch (cdoersch at cs dot cmu dot edu) +% +% if you have a cell array where some cells contain structs of the +% same type and some cells are empty, matlab will crash. This funciton +% gets rid of the empties so that cell2mat can function. +function res=structcell2mat(in) + if(~iscell(in)) + res=in; + return; + end + remove=cellfun(@(x) isempty(x),in); + in(remove)=[]; + res=cell2mat(in); +end