blob: 48203f6ca5110157cfefb7d15e6e7f978ad4532d [file] [log] [blame]
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001/*
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00002 * Copyright (C) 2011 Google Inc.
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
vandebo@chromium.org6112c212011-05-13 03:50:38 +000017#include "SkPDFFormXObject.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000018#include "SkPDFGraphicState.h"
vandebo@chromium.orgf71b2102011-04-04 19:46:31 +000019#include "SkPDFUtils.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000020#include "SkStream.h"
vandebo@chromium.org48543272011-02-08 19:28:07 +000021#include "SkTypes.h"
22
vandebo@chromium.org6112c212011-05-13 03:50:38 +000023static const char* blend_mode_from_xfermode(SkXfermode::Mode mode) {
vandebo@chromium.org48543272011-02-08 19:28:07 +000024 switch (mode) {
25 case SkXfermode::kSrcOver_Mode: return "Normal";
26 case SkXfermode::kMultiply_Mode: return "Multiply";
27 case SkXfermode::kScreen_Mode: return "Screen";
28 case SkXfermode::kOverlay_Mode: return "Overlay";
29 case SkXfermode::kDarken_Mode: return "Darken";
30 case SkXfermode::kLighten_Mode: return "Lighten";
31 case SkXfermode::kColorDodge_Mode: return "ColorDodge";
32 case SkXfermode::kColorBurn_Mode: return "ColorBurn";
33 case SkXfermode::kHardLight_Mode: return "HardLight";
34 case SkXfermode::kSoftLight_Mode: return "SoftLight";
35 case SkXfermode::kDifference_Mode: return "Difference";
36 case SkXfermode::kExclusion_Mode: return "Exclusion";
37
vandebo@chromium.org25adce82011-05-09 08:05:01 +000038 // These are handled in SkPDFDevice::setUpContentEntry.
vandebo@chromium.org48543272011-02-08 19:28:07 +000039 case SkXfermode::kClear_Mode:
40 case SkXfermode::kSrc_Mode:
41 case SkXfermode::kDst_Mode:
42 case SkXfermode::kDstOver_Mode:
43 case SkXfermode::kSrcIn_Mode:
44 case SkXfermode::kDstIn_Mode:
45 case SkXfermode::kSrcOut_Mode:
46 case SkXfermode::kDstOut_Mode:
vandebo@chromium.org6112c212011-05-13 03:50:38 +000047 return "Normal";
48
49 // TODO(vandebo) Figure out if we can support more of these modes.
vandebo@chromium.org48543272011-02-08 19:28:07 +000050 case SkXfermode::kSrcATop_Mode:
51 case SkXfermode::kDstATop_Mode:
52 case SkXfermode::kXor_Mode:
53 case SkXfermode::kPlus_Mode:
54 return NULL;
55 }
56 return NULL;
57}
58
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000059SkPDFGraphicState::~SkPDFGraphicState() {
60 SkAutoMutexAcquire lock(canonicalPaintsMutex());
vandebo@chromium.org6112c212011-05-13 03:50:38 +000061 if (!fSMask) {
62 int index = find(fPaint);
63 SkASSERT(index >= 0);
64 canonicalPaints().removeShuffle(index);
65 }
66 fResources.unrefAll();
67}
68
69void SkPDFGraphicState::getResources(SkTDArray<SkPDFObject*>* resourceList) {
70 resourceList->setReserve(resourceList->count() + fResources.count());
71 for (int i = 0; i < fResources.count(); i++) {
72 resourceList->push(fResources[i]);
73 fResources[i]->ref();
74 fResources[i]->getResources(resourceList);
75 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000076}
77
78void SkPDFGraphicState::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
79 bool indirect) {
80 populateDict();
81 SkPDFDict::emitObject(stream, catalog, indirect);
82}
83
vandebo@chromium.org48543272011-02-08 19:28:07 +000084// static
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000085size_t SkPDFGraphicState::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
86 populateDict();
87 return SkPDFDict::getOutputSize(catalog, indirect);
88}
89
90// static
91SkTDArray<SkPDFGraphicState::GSCanonicalEntry>&
92SkPDFGraphicState::canonicalPaints() {
93 // This initialization is only thread safe with gcc.
94 static SkTDArray<SkPDFGraphicState::GSCanonicalEntry> gCanonicalPaints;
95 return gCanonicalPaints;
96}
97
98// static
99SkMutex& SkPDFGraphicState::canonicalPaintsMutex() {
100 // This initialization is only thread safe with gcc.
101 static SkMutex gCanonicalPaintsMutex;
102 return gCanonicalPaintsMutex;
103}
104
105// static
106SkPDFGraphicState* SkPDFGraphicState::getGraphicStateForPaint(
107 const SkPaint& paint) {
108 SkAutoMutexAcquire lock(canonicalPaintsMutex());
109 int index = find(paint);
110 if (index >= 0) {
111 canonicalPaints()[index].fGraphicState->ref();
112 return canonicalPaints()[index].fGraphicState;
113 }
114 GSCanonicalEntry newEntry(new SkPDFGraphicState(paint));
115 canonicalPaints().push(newEntry);
116 return newEntry.fGraphicState;
117}
118
119// static
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000120SkPDFGraphicState* SkPDFGraphicState::getSMaskGraphicState(
121 SkPDFFormXObject* sMask, bool invert) {
122 // The practical chances of using the same mask more than once are unlikely
123 // enough that it's not worth canonicalizing.
124 SkAutoMutexAcquire lock(canonicalPaintsMutex());
125
126 SkRefPtr<SkPDFDict> sMaskDict = new SkPDFDict("Mask");
127 sMaskDict->unref(); // SkRefPtr and new both took a reference.
128 sMaskDict->insert("S", new SkPDFName("Alpha"))->unref();
129 sMaskDict->insert("G", new SkPDFObjRef(sMask))->unref();
130
131 SkPDFGraphicState* result = new SkPDFGraphicState;
132 result->fPopulated = true;
133 result->fSMask = true;
134 result->insert("Type", new SkPDFName("ExtGState"))->unref();
135 result->insert("SMask", sMaskDict.get());
136 result->fResources.push(sMask);
137 sMask->ref();
138
139 if (invert) {
140 // Acrobat crashes if we use a type 0 function, kpdf crashes if we use
141 // a type 2 function, so we use a type 4 function.
142 SkRefPtr<SkPDFArray> domainAndRange = new SkPDFArray;
143 domainAndRange->unref(); // SkRefPtr and new both took a reference.
144 domainAndRange->reserve(2);
145 domainAndRange->append(new SkPDFInt(0))->unref();
146 domainAndRange->append(new SkPDFInt(1))->unref();
147
148 static const char psInvert[] = "{1 exch sub}";
149 SkRefPtr<SkMemoryStream> psInvertStream =
150 new SkMemoryStream(&psInvert, strlen(psInvert), true);
151 psInvertStream->unref(); // SkRefPtr and new both took a reference.
152
153 SkRefPtr<SkPDFStream> invertFunc =
154 new SkPDFStream(psInvertStream.get());
155 result->fResources.push(invertFunc.get()); // Pass the ref from new.
156 invertFunc->insert("FunctionType", new SkPDFInt(4))->unref();
157 invertFunc->insert("Domain", domainAndRange.get());
158 invertFunc->insert("Range", domainAndRange.get());
159
160 sMaskDict->insert("TR", new SkPDFObjRef(invertFunc.get()))->unref();
161 }
162
163 return result;
164}
165
166// static
167SkPDFGraphicState* SkPDFGraphicState::getNoSMaskGraphicState() {
168 SkAutoMutexAcquire lock(canonicalPaintsMutex());
169 static SkPDFGraphicState* noSMaskGS = NULL;
170 if (!noSMaskGS) {
171 noSMaskGS = new SkPDFGraphicState;
172 noSMaskGS->fPopulated = true;
173 noSMaskGS->fSMask = true;
174 noSMaskGS->insert("Type", new SkPDFName("ExtGState"))->unref();
175 noSMaskGS->insert("SMask", new SkPDFName("None"))->unref();
176 }
177 noSMaskGS->ref();
178 return noSMaskGS;
179}
180
181// static
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000182int SkPDFGraphicState::find(const SkPaint& paint) {
183 GSCanonicalEntry search(&paint);
184 return canonicalPaints().find(search);
185}
186
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000187SkPDFGraphicState::SkPDFGraphicState()
188 : fPopulated(false),
189 fSMask(false) {
190}
191
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000192SkPDFGraphicState::SkPDFGraphicState(const SkPaint& paint)
193 : fPaint(paint),
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000194 fPopulated(false),
195 fSMask(false) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000196}
197
198// populateDict and operator== have to stay in sync with each other.
199void SkPDFGraphicState::populateDict() {
200 if (!fPopulated) {
201 fPopulated = true;
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000202 insert("Type", new SkPDFName("ExtGState"))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000203
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000204 SkRefPtr<SkPDFScalar> alpha =
vandebo@chromium.org1cfa2c42011-01-31 19:35:43 +0000205 new SkPDFScalar(fPaint.getAlpha() * SkScalarInvert(0xFF));
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000206 alpha->unref(); // SkRefPtr and new both took a reference.
207 insert("CA", alpha.get());
208 insert("ca", alpha.get());
209
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000210 SK_COMPILE_ASSERT(SkPaint::kButt_Cap == 0, paint_cap_mismatch);
211 SK_COMPILE_ASSERT(SkPaint::kRound_Cap == 1, paint_cap_mismatch);
212 SK_COMPILE_ASSERT(SkPaint::kSquare_Cap == 2, paint_cap_mismatch);
213 SK_COMPILE_ASSERT(SkPaint::kCapCount == 3, paint_cap_mismatch);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000214 SkASSERT(fPaint.getStrokeCap() >= 0 && fPaint.getStrokeCap() <= 2);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000215 insert("LC", new SkPDFInt(fPaint.getStrokeCap()))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000216
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000217 SK_COMPILE_ASSERT(SkPaint::kMiter_Join == 0, paint_join_mismatch);
218 SK_COMPILE_ASSERT(SkPaint::kRound_Join == 1, paint_join_mismatch);
219 SK_COMPILE_ASSERT(SkPaint::kBevel_Join == 2, paint_join_mismatch);
220 SK_COMPILE_ASSERT(SkPaint::kJoinCount == 3, paint_join_mismatch);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000221 SkASSERT(fPaint.getStrokeJoin() >= 0 && fPaint.getStrokeJoin() <= 2);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000222 insert("LJ", new SkPDFInt(fPaint.getStrokeJoin()))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000223
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000224 insert("LW", new SkPDFScalar(fPaint.getStrokeWidth()))->unref();
225 insert("ML", new SkPDFScalar(fPaint.getStrokeMiter()))->unref();
226 insert("SA", new SkPDFBool(true))->unref(); // Auto stroke adjustment.
vandebo@chromium.org48543272011-02-08 19:28:07 +0000227
228 SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode;
vandebo@chromium.orgf71b2102011-04-04 19:46:31 +0000229 // If asMode fails, default to kSrcOver_Mode.
vandebo@chromium.org48543272011-02-08 19:28:07 +0000230 if (fPaint.getXfermode())
231 fPaint.getXfermode()->asMode(&xfermode);
232 // If we don't support the mode, just use kSrcOver_Mode.
233 if (xfermode < 0 || xfermode > SkXfermode::kLastMode ||
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000234 blend_mode_from_xfermode(xfermode) == NULL) {
vandebo@chromium.org48543272011-02-08 19:28:07 +0000235 xfermode = SkXfermode::kSrcOver_Mode;
vandebo@chromium.orgf71b2102011-04-04 19:46:31 +0000236 NOT_IMPLEMENTED("unsupported xfermode", false);
vandebo@chromium.org48543272011-02-08 19:28:07 +0000237 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000238 insert("BM",
239 new SkPDFName(blend_mode_from_xfermode(xfermode)))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000240 }
241}
242
243// We're only interested in some fields of the SkPaint, so we have a custom
244// operator== function.
245bool SkPDFGraphicState::GSCanonicalEntry::operator==(
246 const SkPDFGraphicState::GSCanonicalEntry& gs) const {
247 const SkPaint* a = fPaint;
248 const SkPaint* b = gs.fPaint;
249 SkASSERT(a != NULL);
250 SkASSERT(b != NULL);
vandebo@chromium.org48543272011-02-08 19:28:07 +0000251
252 if (SkColorGetA(a->getColor()) != SkColorGetA(b->getColor()) ||
253 a->getStrokeCap() != b->getStrokeCap() ||
254 a->getStrokeJoin() != b->getStrokeJoin() ||
255 a->getStrokeWidth() != b->getStrokeWidth() ||
256 a->getStrokeMiter() != b->getStrokeMiter()) {
257 return false;
258 }
259
vandebo@chromium.org48543272011-02-08 19:28:07 +0000260 SkXfermode::Mode aXfermodeName = SkXfermode::kSrcOver_Mode;
vandebo@chromium.org69d4ca32011-05-09 17:34:19 +0000261 SkXfermode* aXfermode = a->getXfermode();
262 if (aXfermode) {
263 aXfermode->asMode(&aXfermodeName);
264 }
265 if (aXfermodeName < 0 || aXfermodeName > SkXfermode::kLastMode ||
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000266 blend_mode_from_xfermode(aXfermodeName) == NULL) {
vandebo@chromium.org69d4ca32011-05-09 17:34:19 +0000267 aXfermodeName = SkXfermode::kSrcOver_Mode;
268 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000269 const char* aXfermodeString = blend_mode_from_xfermode(aXfermodeName);
vandebo@chromium.org69d4ca32011-05-09 17:34:19 +0000270 SkASSERT(aXfermodeString != NULL);
vandebo@chromium.org48543272011-02-08 19:28:07 +0000271
vandebo@chromium.org69d4ca32011-05-09 17:34:19 +0000272 SkXfermode::Mode bXfermodeName = SkXfermode::kSrcOver_Mode;
273 SkXfermode* bXfermode = b->getXfermode();
274 if (bXfermode) {
275 bXfermode->asMode(&bXfermodeName);
276 }
277 if (bXfermodeName < 0 || bXfermodeName > SkXfermode::kLastMode ||
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000278 blend_mode_from_xfermode(bXfermodeName) == NULL) {
vandebo@chromium.org69d4ca32011-05-09 17:34:19 +0000279 bXfermodeName = SkXfermode::kSrcOver_Mode;
280 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000281 const char* bXfermodeString = blend_mode_from_xfermode(bXfermodeName);
vandebo@chromium.org69d4ca32011-05-09 17:34:19 +0000282 SkASSERT(bXfermodeString != NULL);
283
284 return strcmp(aXfermodeString, bXfermodeString) == 0;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000285}