image.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. 'use strict';
  2. const { join } = require('path');
  3. const { updateElementReadonly, updateElementInvalid } = require('../utils/assets');
  4. const { injectionStyle } = require('../utils/prop');
  5. const { ModeMap } = require('./texture/texture');
  6. const imageTypeToImporter = {
  7. raw: '',
  8. texture: 'texture',
  9. 'normal map': 'texture',
  10. 'sprite-frame': 'sprite-frame',
  11. 'texture cube': 'erp-texture-cube',
  12. };
  13. const imageTypeToName = {
  14. raw: '',
  15. texture: 'texture',
  16. 'normal map': 'normalMap',
  17. 'sprite-frame': 'spriteFrame',
  18. 'texture cube': 'textureCube',
  19. };
  20. exports.template = /* html */`
  21. <div class="asset-image">
  22. <ui-prop>
  23. <ui-label slot="label" value="i18n:ENGINE.assets.image.type" tooltip="i18n:ENGINE.assets.image.typeTip"></ui-label>
  24. <ui-select slot="content" class="type-select"></ui-select>
  25. </ui-prop>
  26. <ui-prop>
  27. <ui-label slot="label" value="i18n:ENGINE.assets.image.flipVertical" tooltip="i18n:ENGINE.assets.image.flipVerticalTip"></ui-label>
  28. <ui-checkbox slot="content" class="flipVertical-checkbox"></ui-checkbox>
  29. </ui-prop>
  30. <ui-prop class="fixATA-prop">
  31. <ui-label slot="label" value="i18n:ENGINE.assets.image.fixAlphaTransparencyArtifacts" tooltip="i18n:ENGINE.assets.image.fixAlphaTransparencyArtifactsTip"></ui-label>
  32. <ui-checkbox slot="content" class="fixAlphaTransparencyArtifacts-checkbox"></ui-checkbox>
  33. </ui-prop>
  34. <ui-prop>
  35. <ui-label slot="label" value="i18n:ENGINE.assets.image.flipGreenChannel" tooltip="i18n:ENGINE.assets.image.flipGreenChannelTip"></ui-label>
  36. <ui-checkbox slot="content" class="flipGreenChannel-checkbox"></ui-checkbox>
  37. </ui-prop>
  38. <ui-prop class="isRGBE-prop">
  39. <ui-label slot="label" value="i18n:ENGINE.assets.image.isRGBE" tooltip="i18n:ENGINE.assets.image.isRGBETip"></ui-label>
  40. <ui-checkbox slot="content" class="isRGBE-checkbox"></ui-checkbox>
  41. </ui-prop>
  42. <ui-section expand class="sub-panel-section config no-padding" cache-expand="image-sub-panel-section">
  43. <ui-label slot="header"></ui-label>
  44. <ui-panel></ui-panel>
  45. </ui-section>
  46. <ui-section expand class="sub-texture-panel-section config no-padding" cache-expand="image-sub-panel-section" hidden>
  47. <ui-label slot="header"></ui-label>
  48. <ui-panel></ui-panel>
  49. </ui-section>
  50. </div>
  51. `;
  52. exports.style = /* css */`
  53. :host .asset-image > ui-prop { margin-right: 4px; }
  54. `;
  55. exports.$ = {
  56. container: '.asset-image',
  57. typeSelect: '.type-select',
  58. flipVerticalCheckbox: '.flipVertical-checkbox',
  59. flipGreenChannel: '.flipGreenChannel-checkbox',
  60. fixAlphaTransparencyArtifactsCheckbox: '.fixAlphaTransparencyArtifacts-checkbox',
  61. fixATAProp: '.fixATA-prop',
  62. isRGBEProp: '.isRGBE-prop',
  63. isRGBECheckbox: '.isRGBE-checkbox',
  64. panelSection: '.sub-panel-section',
  65. texturePanelSection: '.sub-texture-panel-section',
  66. };
  67. const Elements = {
  68. type: {
  69. ready() {
  70. const panel = this;
  71. panel.$.typeSelect.addEventListener('change', async (event) => {
  72. panel.metaList.forEach((meta) => {
  73. meta.userData.type = event.target.value;
  74. });
  75. await panel.changeSubMetaWithType();
  76. // There are other properties whose updates depend on its changes attribute corresponds to the edit element
  77. Elements.isRGBE.update.call(panel);
  78. Elements.fixAlphaTransparencyArtifacts.update.call(panel);
  79. // imageAssets type change to spriteFrame, update mipmaps
  80. panel.updatePanel();
  81. // need to be dispatched after updatePanel
  82. panel.dispatch('change');
  83. });
  84. panel.$.typeSelect.addEventListener('confirm', () => {
  85. panel.dispatch('snapshot');
  86. });
  87. },
  88. update() {
  89. const panel = this;
  90. let optionsHtml = '';
  91. const types = ['raw', 'texture', 'normal map', 'sprite-frame', 'texture cube'];
  92. types.forEach((type) => {
  93. optionsHtml += `<option value="${type}">${type}</option>`;
  94. });
  95. panel.$.typeSelect.innerHTML = optionsHtml;
  96. panel.$.typeSelect.value = panel.meta.userData.type;
  97. updateElementInvalid.call(panel, panel.$.typeSelect, 'type');
  98. updateElementReadonly.call(panel, panel.$.typeSelect);
  99. },
  100. },
  101. flipVertical: {
  102. ready() {
  103. const panel = this;
  104. panel.$.flipVerticalCheckbox.addEventListener('change', (event) => {
  105. panel.metaList.forEach((meta) => {
  106. meta.userData.flipVertical = event.target.value;
  107. });
  108. panel.dispatch('change');
  109. });
  110. panel.$.flipVerticalCheckbox.addEventListener('confirm', () => {
  111. panel.dispatch('snapshot');
  112. });
  113. },
  114. update() {
  115. const panel = this;
  116. panel.$.flipVerticalCheckbox.value = panel.meta.userData.flipVertical;
  117. updateElementInvalid.call(panel, panel.$.flipVerticalCheckbox, 'flipVertical');
  118. updateElementReadonly.call(panel, panel.$.flipVerticalCheckbox);
  119. },
  120. },
  121. fixAlphaTransparencyArtifacts: {
  122. ready() {
  123. const panel = this;
  124. panel.$.fixAlphaTransparencyArtifactsCheckbox.addEventListener('change', (event) => {
  125. panel.metaList.forEach((meta) => {
  126. meta.userData.fixAlphaTransparencyArtifacts = event.target.value;
  127. });
  128. panel.dispatch('change');
  129. });
  130. panel.$.fixAlphaTransparencyArtifactsCheckbox.addEventListener('confirm', () => {
  131. panel.dispatch('snapshot');
  132. });
  133. },
  134. update() {
  135. const panel = this;
  136. const fixAlphaTransparencyArtifactsCheckbox = panel.$.fixAlphaTransparencyArtifactsCheckbox;
  137. const fixATAProp = panel.$.fixATAProp;
  138. fixAlphaTransparencyArtifactsCheckbox.value = panel.meta.userData.fixAlphaTransparencyArtifacts;
  139. const bannedTypes = ['normal map'];
  140. const isCapableToFixAlphaTransparencyArtifacts = !bannedTypes.includes(panel.meta.userData.type);
  141. if (isCapableToFixAlphaTransparencyArtifacts) {
  142. fixATAProp.style.display = 'block';
  143. updateElementInvalid.call(panel, panel.$.fixAlphaTransparencyArtifactsCheckbox, 'fixAlphaTransparencyArtifacts');
  144. updateElementReadonly.call(panel, panel.$.fixAlphaTransparencyArtifactsCheckbox);
  145. } else {
  146. fixATAProp.style.display = 'none';
  147. }
  148. },
  149. },
  150. isRGBE: {
  151. ready() {
  152. const panel = this;
  153. panel.$.isRGBECheckbox.addEventListener('change', (event) => {
  154. panel.metaList.forEach((meta) => {
  155. meta.userData.isRGBE = event.target.value;
  156. });
  157. panel.dispatch('change');
  158. });
  159. panel.$.isRGBECheckbox.addEventListener('confirm', () => {
  160. panel.dispatch('snapshot');
  161. });
  162. },
  163. update() {
  164. const panel = this;
  165. if (panel.meta.userData.type === 'texture cube') {
  166. panel.$.isRGBEProp.style.display = 'block';
  167. panel.$.isRGBECheckbox.value = panel.meta.userData.isRGBE;
  168. updateElementInvalid.call(panel, panel.$.isRGBECheckbox, 'isRGBE');
  169. updateElementReadonly.call(panel, panel.$.isRGBECheckbox);
  170. } else {
  171. panel.$.isRGBEProp.style.display = 'none';
  172. }
  173. },
  174. },
  175. flipGreenChannel: {
  176. ready() {
  177. const panel = this;
  178. panel.$.flipGreenChannel.addEventListener('change', (event) => {
  179. panel.metaList.forEach((meta) => {
  180. meta.userData.flipGreenChannel = event.target.value;
  181. });
  182. panel.dispatch('change');
  183. panel.dispatch('snapshot');
  184. });
  185. },
  186. update() {
  187. const panel = this;
  188. panel.$.flipGreenChannel.value = panel.meta.userData.flipGreenChannel;
  189. updateElementInvalid.call(panel, panel.$.flipGreenChannel, 'flipGreenChannel');
  190. updateElementReadonly.call(panel, panel.$.flipGreenChannel);
  191. },
  192. },
  193. };
  194. exports.methods = {
  195. updatePanel() {
  196. this.setPanel(this.$.panelSection, this.meta.userData.type);
  197. // sprite-frame 需要多显示 texture 面板
  198. if (this.meta.userData.type === 'sprite-frame') {
  199. this.setPanel(this.$.texturePanelSection, 'texture');
  200. } else {
  201. this.$.texturePanelSection.style.display = 'none';
  202. }
  203. },
  204. setPanel($section, type) {
  205. const assetList = [];
  206. const metaList = [];
  207. const imageImporter = imageTypeToImporter[type];
  208. this.assetList.forEach((asset) => {
  209. if (!asset) {
  210. return;
  211. }
  212. for (const subUuid in asset.subAssets) {
  213. const subAsset = asset.subAssets[subUuid];
  214. if (!subAsset || subAsset.importer === '*') {
  215. continue;
  216. }
  217. if (subAsset.importer === imageImporter) {
  218. assetList.push(subAsset);
  219. break;
  220. }
  221. }
  222. });
  223. this.metaList.forEach((meta) => {
  224. if (!meta) {
  225. return;
  226. }
  227. for (const subUuid in meta.subMetas) {
  228. const subMeta = meta.subMetas[subUuid];
  229. if (!subMeta || subMeta.importer === '*') {
  230. continue;
  231. }
  232. if (subMeta.importer === imageImporter) {
  233. metaList.push(subMeta);
  234. break;
  235. }
  236. }
  237. });
  238. if (!assetList.length || !metaList.length) {
  239. $section.style.display = 'none';
  240. return;
  241. } else {
  242. $section.style.display = 'block';
  243. }
  244. const asset = assetList[0];
  245. const $label = $section.querySelector('ui-label');
  246. $label.setAttribute('value', asset.name);
  247. const $panel = $section.querySelector('ui-panel');
  248. $panel.setAttribute('src', join(__dirname, `./${asset.importer}.js`));
  249. $panel.injectionStyle(injectionStyle);
  250. $panel.update(assetList, metaList, this.assetList);
  251. },
  252. changeMipFilter(targetSubMetaKey) {
  253. if (this.originImageType === 'sprite-frame') {
  254. // spriteFrame -> any
  255. this.metaList.forEach(async (meta) => {
  256. if (!meta.subMetas[targetSubMetaKey]) {
  257. meta.subMetas[targetSubMetaKey] = {
  258. userData: {},
  259. }
  260. }
  261. // hack only record the configuration of the type that has been updated, not the accurate configuration of the child resources after import.
  262. // If targetSubMeta does not have mipfilter or miupfilter is none, set mipfilter to nearest
  263. const preMipfilter = await Editor.Profile.getTemp('inspector', `${meta.uuid}@${targetSubMetaKey}.texture.mipfilter`, 'default');
  264. if (!preMipfilter || preMipfilter === 'none') {
  265. meta.subMetas[targetSubMetaKey].userData.mipfilter = 'nearest';
  266. }
  267. });
  268. } else if (this.meta.userData.type === 'sprite-frame') {
  269. // any -> sprite,disabled mipmaps
  270. this.metaList.forEach((meta) => {
  271. if (!meta.subMetas[targetSubMetaKey]) {
  272. meta.subMetas[targetSubMetaKey] = {
  273. userData: {},
  274. }
  275. }
  276. meta.subMetas[targetSubMetaKey].userData.mipfilter = 'none';
  277. });
  278. }
  279. },
  280. async changeSubMetaWithType() {
  281. // any -> texture : texture.wrapMode -> Repeat
  282. // any -> sprite : texture.wrapMode -> Clamp
  283. if (['sprite-frame', 'texture'].includes(this.meta.userData.type)) {
  284. const textureKey = Editor.Utils.UUID.nameToSubId('texture');
  285. let wrapModeCache;
  286. if (this.meta.subMetas[textureKey]) {
  287. const textureUUID = this.meta.subMetas[textureKey].uuid;
  288. wrapModeCache = await Editor.Profile.getTemp('inspector', `${textureUUID}.texture.wrapMode`);
  289. if (!wrapModeCache) {
  290. // use default wrapMode if not changed
  291. const wrapModeName = this.meta.userData.type === 'texture' ? 'Repeat' : 'Clamp';
  292. this.metaList.forEach((meta) => {
  293. const data = ModeMap.wrap[wrapModeName];
  294. if (!meta.subMetas[textureKey]) {
  295. meta.subMetas[textureKey] = {
  296. userData: {}
  297. }
  298. }
  299. for (const key of Object.keys(data)) {
  300. meta.subMetas[textureKey].userData[key] = data[key];
  301. }
  302. });
  303. }
  304. }
  305. if (this.originImageType === 'sprite-frame' || this.meta.userData.type === 'sprite-frame') {
  306. const changeTypes = ['texture', 'normal map', 'texture cube', 'sprite-frame'];
  307. if (!changeTypes.includes(this.meta.userData.type)) {
  308. return;
  309. }
  310. let targetName = imageTypeToName[this.meta.userData.type];
  311. if (targetName === 'spriteFrame') {
  312. // change texture asset when import as sprite
  313. targetName = 'texture';
  314. }
  315. const targetSubMetaKey = Editor.Utils.UUID.nameToSubId(targetName);
  316. targetSubMetaKey && this.changeMipFilter(targetSubMetaKey);
  317. }
  318. }
  319. },
  320. };
  321. exports.ready = function() {
  322. for (const prop in Elements) {
  323. const element = Elements[prop];
  324. if (element.ready) {
  325. element.ready.call(this);
  326. }
  327. }
  328. this.$.panelSection.addEventListener('change', () => {
  329. this.dispatch('change');
  330. });
  331. this.$.panelSection.addEventListener('snapshot', () => {
  332. this.dispatch('snapshot');
  333. });
  334. this.$.texturePanelSection.addEventListener('change', () => {
  335. this.dispatch('change');
  336. });
  337. this.$.texturePanelSection.addEventListener('snapshot', () => {
  338. this.dispatch('snapshot');
  339. });
  340. };
  341. exports.update = function(assetList, metaList) {
  342. this.assetList = assetList;
  343. this.metaList = metaList;
  344. this.asset = assetList[0];
  345. this.meta = metaList[0];
  346. this.originImageType = this.meta.userData.type;
  347. for (const prop in Elements) {
  348. const element = Elements[prop];
  349. if (element.update) {
  350. element.update.call(this);
  351. }
  352. }
  353. this.updatePanel();
  354. };